可変長引数の関数の作り方をまとめる。
可変長引数の関数の宣言と実装
仮引数の可変にする部分を...で書く。
// プロトタイプ宣言
// 第一引数で文字列をもらって、第二引数以降は型も引数の数も特に指定しない
void func(char *arg_1, ...);
// 実装
// 第一引数で文字列をもらって、第二引数以降は型も引数の数も特に指定しない例
void func(char *arg_1, ...) {
~処理~
}
①va_listの定義
stdarg.hでは可変数個の引数をリストとして格納できる型としてva_list型が定義されている。
まずは可変長引数の関数内でva_list型の変数を定義する。
va_list ap;
②va_listの初期化
va_listが指す先を可変長個の引数の最初1個目に向けてやる。
これにはstdarg.hで定義されているva_start()マクロを使う。
// apは①で定義したva_list型の変数、arg_1は引き数リストのうち、可変な部分の直前に置かれる引き数
va_start(ap, arg_1);
例えば次のようなコードがあったとすると
void func(char *arg_1, ...) {
va_list ap;
va_start(ap, arg_1);
~処理~
}
int main(void) {
char *arg_1 = "aaaaa";
int arg_2 = 5;
double arg_3 = 3.14;
func(arg_1, arg_2, arg_3);
}
func呼び出すと、実引数は下記のようにスタックに積まれ(Cでは実引数を後ろから積んでいくため)、
va_start(ap, arg_1)が実行されて、apは初期化され、"apが指す位置"はarg_1になる。
スタックのイメージ図
|arg_1| <---apが指す位置
|arg_2|
|arg_3|
③引数リストから値を取り出す
引数リストから値を取り出すにはva_arg()マクロを使う。
va_arg()マクロは"apが指す位置"の次の位置にある値を指定した型で取り出す。
その後、"apが指す位置"を次の位置(取り出した値の位置)に進める。
// apは①で定義し、②で初期化したva_list型の変数、typeは取り出す引数の型
va_arg(ap, type);
例えば次のようなコードがあったとすると
void func(char *arg_1, ...) {
va_list ap;
va_start(ap, arg_1);
printf("arg_2: %d\n", va_arg(ap, int));
printf("arg_3: %lf\n", va_arg(ap, double));
~処理~
}
int main(void) {
char *arg_1 = "aaaaa";
int arg_2 = 5;
double arg_3 = 3.14;
func(arg_1, arg_2, arg_3);
}
"apが指す位置"は下記のように進んでいく。
スタックのイメージ図
va_start(ap, arg_1)を実行した直後
|arg_1| <---apが指す位置
|arg_2|
|arg_3|
va_arg(ap, int)を実行した直後
|arg_1|
|arg_2| <---apが指す位置
|arg_3|
va_arg(ap, double)を実行した直後
|arg_1|
|arg_2|
|arg_3| <---apが指す位置
④使用後のva_list 型変数の処分
manによると「va_start() が実行される毎に、同じ関数内で対応する va_end() が実行されなければならない。」とある。
引数リストの解析が終わったら、va_end() を呼んでやる必要があるようだ。
void func(char *arg_1, ...) {
va_list ap;
va_start(ap, arg_1);
~処理~
va_end(ap);
}
引数の数が分からないため、va_argを何回呼べばいいか分からない
この問題のために、可変長引数関数を作る際には次のような工夫が必要なようだ。
(i) printfのように可変でない部分の引数で引数の数を特定できるようにする。
つまり、printfであれば、第一引数が"%s---%d---%lf\n"となっていれば、その後に続く引数の数は3つだとわかる。
(ii) 引数リストに番兵(NULLとか)をいれ、それをチェックしてやる。
引数の型が分からないため、va_arg(ap, type)のtypeに何を指定していいか分からない
この問題のために、可変長引数関数を作る際には次のような工夫が必要なようだ。
(i) printfのように可変でない部分の引数で引数の型を特定できるようにする。
つまり、printfであれば、第一引数が"%s---%d---%lf\n"となっていれば、その後に続く引数の型はchar*型、int型、double型だとわかる。
参考