名前
stdarg, va_start, va_arg, va_end, va_copy - 個数・型が可変な引数リスト
書式
#include <stdarg.h>
void va_start(va_list ap, last); type va_arg(va_list ap, type); void va_end(va_list ap); void va_copy(va_list dest, va_list src);
説明
関数は呼び出しに際して、個数や型が可変な引数をとることができる。 インクルードファイル <stdarg.h> では va_list 型が宣言されており、3 つのマクロが定義されている。これらを用いると、 呼び出された関数側では個数や型を知らない引き数のリストを、順に一 つづつ読み込むことができる。
呼び出される関数では、 va_list 型のオブジェクトが宣言されていなければならない。このオブジェクトが va_start(), va_arg(), va_end() の各マクロによって扱われる。
va_start
va_start() マクロは最初に呼び出さなければならない。これは ap を初期化し、 va_arg() と va_end() で用いることができるようにする。
パラメーター last は引き数リストのうち、可変な部分の直前に置かれるパラメーターの名前であ る。つまり呼び出された関数が型を知っている最後の引き数である。
このパラメーターはレジスタ変数や関数、配列として 宣言してはならない。このパラメーターのアドレスが va_start() マクロで用いられるかもしれないからである。
va_arg
va_arg() マクロは、呼び出し時に指定された引き数のうち、 次の位置にあるものを指定した型 type の値として取得する。 パラメーター ap は va_list ap で、 va_start() によって初期化されている必要がある。 va_arg() を呼び出すごとに ap は変更され、次回の呼び出しの際に、さらに次の引き数を返すようになる。 パラメーター type は型の名前である。 type の前に * を付ければ、オブジェクトへの型付きポインタが得られる。
va_start() マクロの直後に va_arg() を最初に実行すると、 last の次の引き数が返る。続けて実行すると、残りの引き数がそれぞれ返る。
次の引き数がなかったり、 type が次の引き数の実際の型と互換でない場合 (デフォルトの引き数変換で扱 えなかった場合) には、予測できないエラーが起こる。
ap が va_arg(ap,type) の形で関数に渡されると、 ap の値は関数から返って来た後は不定となる。
va_end
va_start() が実行される毎に、同じ関数内で対応する va_end() が実行されなければならない。 va_end(ap) が呼び出された後、変数 ap の値は不定となる。 va_start() と va_end() の組を何回も並べて使うことも可能である。 va_end() はマクロかもしれないし関数かもしれない。
va_copy
すぐ分かる va_list の実装は、variadic な関数のスタックフレームのポインタである。 このような場合(ほとんどはそうである)、 単に以下のようにすればいいように思える。
va_list aq = ap;
残念ながら、(長さ 1の)ポインタの配列として扱うシステムもある。 そのような場合、以下のようにする必要がある。va_list aq; *aq = *ap;
最後に、パラメータをレジスタで渡すシステムの場合、 va_start() でメモリを割り当て、パラメータを格納し、 次のパラメータがどれかを指し示すようにする必要がある。 そして va_arg() でリストを順番にたどり、 va_end() で割り当てたメモリを開放する。 このような状況に対応するため、C99 では va_copy() マクロを追加し、 前述のような割り当ては以下のように置き換えられるようにした。va_list aq; va_copy(aq, ap); ... va_end(aq);
va_copy() が実行されるごとに、 対応する va_end() を同じ関数内で実行しなければならない。 この名前はまだ draft proposal なので、 va_copy() の代わりに __va_copy を用いるシステムもある。準拠
va_start(), va_arg(), va_end() マクロは C89 準拠である。 va_copy() は C99 で定義されている。
注意
これらのマクロは、以前から用いられてきた同等のマクロ群と 互換ではない。過去のものと互換なバージョンは、 インクルードファイル varargs.h に存在する。
歴史的なセットアップは以下のとおりである。
#include <varargs.h>
void
foo(va_alist)
va_dcl
{
va_list ap;
va_start(ap);
while(...) {
...
x = va_arg(ap, type);
...
}
va_end(ap);
}
バグ
varargs マクロとは異なり、 stdarg マクロでは固定引き数なしで関数を指定することが許されていない。 これは varargs ベースのコードを stdarg のコードに書き換えるときに、面倒な作業のもとになる。 また、すべての引き数を va_list として可変個指定したいような場合 (vfprintf(3) など) にも障害となる。
例
関数 foo は書式文字からなる文字列を受け入れ、その書式文字に対応する型で可変個の 引き数を読み込み、印字する。
#include <stdio.h>
#include <stdarg.h>
void
foo(char *fmt, ...)
{
va_list ap;
int d;
char c, *s;
va_start(ap, fmt);
while (*fmt)
switch(*fmt++) {
case s: /* string */
s = va_arg(ap, char *);
printf("string %s\n", s);
break;
case d: /* int */
d = va_arg(ap, int);
printf("int %d\n", d);
break;
case c: /* char */
/* need a cast here since va_arg only
takes fully promoted types */
c = (char) va_arg(ap, int);
printf("char %c\n", c);
break;
}
va_end(ap);
}