C 言語では可変引数の関数を作ることができるし、使ってもいる(printf とか)けど、自分で書いたことはなかったので書いてみました。
#includevoid func(int argc, ...) { va_list argv; int a; char* b; double c; va_start(argv, argc); if (argc >= 1) { a = va_arg(argv, int); printf("%dn", a); } if (argc >= 2) { b = va_arg(argv, char*); printf("%sn", b); } if (argc >= 3) { c = va_arg(argv, double); printf("%fn", c); } va_end(argv); printf("------------------------n"); } void main() { func(3, 100, "aaa", 10.5); func(1, 200); }
ポイントは4つ
- 可変引数部分の仮引数宣言は...で表現する。
- 可変引数の取り出しには、va_start/va_arg/va_end 等のマクロを使用する。
- 可変引数がいくつ渡されたのかを直接知る方法はないので、いくつ渡したかは何らかの方法で伝える必要がある。
- 必ず1つは普通の引数(上記だと argc)が必要。
--------------------
あと、va_list/va_start/va_arg は大体何やっているか想像がついたけど、va_end がなにをやっているかどうしても想像できない。
しかし、ぐぐっても「"va_end" は必ず指定しましょう」的な記述しかなく、その効用が書かれているものがない…
そこで、K&R 本(プログラミング言語C 第2版)を見ても、最後は va_end 実行するように書かれていたが、何をしているのかは書かれていなかった(汗
仕方ないので、VC++ の Header を覗いてみた。すると以下。
#define va_start _crt_va_start #define va_arg _crt_va_arg #define va_end _crt_va_end
GCC 系の Header も見つかったので、そちらを見るとこんな感じ。
#define va_start(v,l) __builtin_va_start(v,l) #define va_end(v) __builtin_va_end(v) #define va_arg(v,l) __builtin_va_arg(v,l)
ともに参考にならない…(汗
そこで LSI-C 試食版をダウンロードして確認すると以下。
typedef void *va_list; #define va_start(ap, pn) (ap = (va_list)(&pn + 1)) #define va_arg(ap, t) (*(*(t **)&ap)++) #define va_end(ap)
ああやっぱり、va_end って消えるのね…なんとなく納得。
てことは、va_end は「お作法」だった、ってことですかね…
ただ、今の実装を確認すると va_list は null になるようです。(VS2013 の VC++ で確認)
もしかすると、これに加えてセキュリティ対策などで何かしらの終了処理があったりするのかもしれない。この辺までは確認していません。