可変長引数

可変長引数をとる関数をつくるには,va_*マクロを使う. 大雑把な流れは以下のとおり.

foo(type1 x, ...)
{
    va_list args;
    va_start(args, x);
    while (ナントカ) {
        type2 arg = va_arg(args, type2);
    }
    va_end(args);

詳細

具体例がないとわかりにくいと思うので,次のような関数を例に説明する.

プロトタイプ int sum(int num, ...);
機能 int型の引数群の合計値を計算する
引数 第1引数num:可変長引数の個数.
第2引数以降(...):合計を計算したいint型の並び.
返値 第2引数以降で指定したnum個のint型変数・定数の合計値
使用例 3つの整数{10,11,12}の合計を計算する場合,次のように呼び出す.
sum(3, 10, 11, 12);

可変長引数をとる関数といっても,少なくとも最初の1個の引数(上の例ではint numの部分)は明確になっていなければならない [理由についてはあとの説明を読み進めるとわかります]. したがって,可変長引数をとる関数は必ず“foo(type x, ...)のような形式になる. [なので,可変長引数をとる関数というのは,正確には1個以上の可変個の引数をとる関数といえます. おなじみのprintf(もっともよく知られている可変長引数をとるC言語標準関数)も最初の1個の引数だけは任意ではなくて, 必ず書式文字列がくることになっていますよね.]

この関数を実装すると,だいたい次のようになる.

#include <stdarg.h>

int sum(int num, ...)
{
    int result = 0;
    int ival, i;
    va_list args;

    va_start(args, num);

    for (i = 0; i < num; ++i) {
        ival = va_arg(args, int);
        result += ival;
    }

    va_end(args);

    return result;
}
マクロ 説明
va_list args; 引数のリストを保持するための変数argsを宣言する. 今後このargsを介して可変長引数部分にアクセスする.
va_start(args, num); argsの初期化. 第2引数にnumを与えているのは,numが可変長引数が始まる位置の目印になるため. つまり,可変長引数部分の直前の引数を与える必要がある.
va_arg(args, int); 次の引数を型typeとみなして読み込んでくる. この例では可変長引数部分はすべてintと決め込んで読み込んでいるけど, printfみたいに第1引数でどの型として読み込むべきかについてのヒントをもらって いろんな型の足し算ができるようにしたらステキかも知れない.
va_end(args); 後始末.[これってほったらかしにしたらいけないの?と思いたくなるけど…]

実験用リソース

可変長引数ではfloatdoubleに変換される

double f = 3.15;
printf("%f\n", f);

このプログラムは普通に見るものだけど,よく考えてみるとちょっとしたミステリーが潜んでいる. 第1引数の書式文字列“%f”をみて,第2引数を浮動小数点数として処理しようとするのだけど, printf関数はffloat型とdouble型のどちらなのかはどうやって判別してるの? floatdoubleは最初の1ビットが符号部であることをのぞいてバイト数も書式もちがってるから, あらかじめどちらの型なのかわかっていないと正しい表示ができなくなってしまう.

実は可変長引数では,必ずfloat型はdouble型に変換されてから関数にわたされるという規則になっている. したがって,可変長引数を受けとる側の関数は,必ずdoubleがわたってくると仮定して処理すればよい. 他にもchar型はint型に変換されてからわたされるというような規則がある. 詳細については以下の資料を参照.


はたいたかし
2006-05-05 初稿
2006-09-12 可変長引数における型変換(floatdoublecharint)について追記
トップ > 開発ツール > C/C++