arrow 若葉プログラミング塾 > 知識の玉手箱 > C関数リファレンス >
va_arg()
va_arg()

このマクロの目的

va_argマクロは、可変長引数を取得する。

定義

	#include <stdarg.h>
	type va_arg(va_list ap, type);

働き

このマクロは、二番目の引数と同じ型の返り値を持つ表現(expression)に展開される。 va_list 型の apva_start() で初期化されたものである。 va_arg の実行によって、 ap は順番に引数を返すように変更される。 引数の型 type は、実際の引数の型へのポインタ型であり、特定された型は type の前に*を添えることで取得できるように決定される。 ありもしない引数を取得しようとしたり、引数の型が違っていた場合の振る舞いは未定義である。

va_start() 後の最初の実行では、 va_start() で指定した parmN の直後の引数の値を返す。 続けて実行すれば、残りの引数の値を順番に返す。

解説

このマクロで実際に引数の値を得るわけだが、注意しなければならないのは、可変長の引数をとる関数自身は、どんな型でどんな数の引数が渡されたかを直接知る方法はないということである。 これは、呼び出し側が可変長部分より前の引数を使って関数側に教えてやらなくてはならないのだ。

この分かりやすい例が、初心者のお気に入り printf() である。 この関数は次のようにプロトタイプ宣言されている。

	int printf(const char *format, ...);

呼び出し側は、省略されている部分へ適切な型の引数が渡るように format を使って関数側に教えてやる必要がある。 意識していないかもしれないが、 %d%s%f といった変換指定文字列は、これをしているのである。

したがって、もし format と引数の型が矛盾していたとしても、コンパイラはエラーを出さないため、実行時に動作が変になって矛盾に気づくということが多いのである。 よくあるのが、

	printf("%s");

というような呼び出しで、プログラムが異常終了するという場合である。 printf 関数は、可変長引数に文字列へのポインタが渡されていると思い込んでいるのだが、実際には何も渡されていない。 よって、どこからともなく(実際にはスタックの一部)ポインタの値を無理やり引っ張ってきて、とんでもない場所から文字列を読み出そうとする。 メモリ保護機能のあるシステムなら、これをアクセス違反として異常終了する。 保護機能が無ければシステムの重要な部分を書き換えても平然と実行を続けてしまう。 ああ恐い!(本当は printf 関数は書き換えないが、ヌル文字に出会うまで延々と出力を続ける)

これは、可変長引数関数を使う限り避けようのない問題である。 チームで開発していたとして、呼び出し側の担当者がどんなまぬけでも、信頼してやるしかないのである。 この問題のために、可変長引数関数は名前ほどには役には立たない。

可変長引数関数を扱う上でもう一つ重要なのが、標準変換である。 省略された引数に渡された整数型で int より小さいものは int に、 float は double に暗黙のうちに変換される。 よって va_arg(ap, short)va_arg(ap, float) といった記述は正しく動かない(警告を出してくれる処理系はある)。 これは算術演算(+-*/%など)においても同様である。 よく printf%f 指定子を float 、 %lf を double に対応しているのだと思っている人がいるが、 そもそも可変長引数には float は渡せないので、 %f も double として扱われてしまうのである。 この仕様は、 scanf の指定子が %f を float 、 %lf を double 、 %Lf を long double へのポインタを意味するものと解釈するのと非対称であり、しばしば混乱を招く。

この標準変換の仕様は、その昔 ANSI C が存在しなかった頃に関数のプロトタイプ宣言という習慣が存在せず、引数は適当な扱いやすい型にに変換されるものであったことによる。 その頃は、代わりに、 int func() のように関数の返り値だけを宣言していた。 仮引数に何も書かないと、引数全体を可変長として宣言するのと同様の結果になるのは、このような宣言を含む古いソースコードとの互換性を保つためである。

標準変換のため、古い C では float を関数の実引数に渡すのは精度を落とすだけで無意味であったが、プロトタイプ宣言をしていれば標準変換を働かないようにしてくれる処理系もあるので、 引数の float に意味が無いという伝説はプロトタイプ宣言をしている限りでは正しくない。

arrow 若葉プログラミング塾 > 知識の玉手箱 > C関数リファレンス >
KC