関数プロトタイプの混乱④
プロトタイプ宣言の中でも動的引数の宣言方法はかなり特殊です。printfの定義で使われているのですが、とりあえず動的引数のサンプルを見てみたいと思います。
Pattern 11
#include <stdio.h> #include <stdarg.h> int func(int arg_num, ... ); int main(){ func(5, 4, 3, 2, 1, 0); } int func(int arg_num, ... ){ int i = 0; va_list list; va_start(list, arg_num); for(i=0; i<arg_num; i++ ) printf("%d\n", va_arg(list, int)); va_end(list); return(0); }
これはCでもC++でもコンパイルできます。この仕様にCとC++の差異はありません。動的引数の場合第一引数を必ずおきます。動的引数を使う場合は、stdarg.hをincludeしてそこで定義されている関数マクロ、va_start, va_arg, va_end と 型定義された va_listの4つを使います。va_listは1byteを指すアドレスです。va_startで第一引数の隣のアドレスを取得します。va_argは今のアドレスが指す値を戻り値として、次の引数を指すアドレスをlistに格納します。va_endはlistを解放します。(ローカルでlistを定義している場合はva_endは必要ないのではないかと僕は考えています。) 動的引数を使う場合の注意を書いておきます。
①va_argは引数のアドレスを隣から隣へと次々と引っ張り出していきますが終端文字といったものがないので何処で引数が終わるか判りません。引数の最後に終端を定義するか(例えばNULLとか)、第一引数で引数の数を指定します。
②第一引数以外の引数の型はあらかじめ知っておかないとva_argは隣のアドレスへのポインタを正確に返せません。第一引数以外の全ての引数の型を統一するか、第一引数でその後の引数の型を指定します。 (実はスタックの最小単位がintなので型情報はほぼ統一されます。正確にはスタックにつまれた時の型のサイズを知っておく必要があります。)
printfを思い起こしてください。第一引数がダブルクォーテーションで囲まれた文字列リテラルです。リテラルを引数で渡すとポインタの先頭アドレスが渡されますが、このリテラルの中に、引数の数、型の情報を入れていますよね。"a = %d, b = %d" とあれば引数は二つ、型はintだって判ります。動的引数の仕様がそのままprintfのためにあるようなものです。正直動的引数を自分で定義して使っている人は見たことがありませんので、今回は使い方だけ理解していただければと思います。