前の記事でstdarg.h
を使っていた中で、
va_arg()
マクロで"型"を引数として受け取っている点が気になったので少し調べてみた。
includeしたマクロの詳細が気になるのは人間の性。
"型"を引数に受け取るマクロの実装となって最初に思いついたのはtypeof
だった。
#define cast(src, type) ((typeof(type))src)
これで一応src
に渡した値をtype
の型にキャスト出来た。
ではva_arg
はどういった実装だろうか。
まずはstdarg.h
のmanを読んでみる。
すると、va_arg
は
type va_arg(va_list ap, type);
va_arg() マクロは、呼び出しの次の引数の型と値がある式に展開されます。 パラメータ ap は、 va_start() によって初期化される va_listap です。 va_arg() の呼び出しごとに ap は修正され、次の呼び出しが次の引数を返します。 パラメータ type は、指定型のオブジェクトを指すポインタの型が * を type に追加するだけで得られるように指定された型名です。
次の引数がない場合、または type が (デフォルトの引数生成に従って生成されたような) 実際の次の引数の型と互換性がない場合、 ランダムなエラーが発生します。
と定義されている。
ここで気になるパラメータはtype
である。
要約すると引数の型へのポインタ型であり、*
をつければ実際の型が得られる
と言うような定義である。
なるほど。
定義はなんとなくわかったので、どうなっているかを見てみる。
まずはstdarg.h
を読む
includeしたファイルのソースを読みたくなったら、gccのオプションを使って
$ gcc -H hoge.c
とする事で、ソースファイルの名前がわかる。
自分の環境では. /usr/lib/gcc/x86_64-linux-gnu/7/include/stdarg.h
にあった。
早速読んでみると、
#define va_arg(v, l) __builtin_va_arg(v, l)
とある。
う〜ん…
gccのソース内のbuiltin系を読んでみる。
調べてみると一部の環境では以下のような実装だとわかった。
__builtin_va_arg(ap, t) (*(*(t **)&ap)++)
これは意外とわかりやすい。
この方式で実装し直してみると
#define cast(src, type) (*(type *)&src)
となる。
なかなか綺麗だ。
が、これはマクロである事を忘れてはいけない。
そう考えるとそもそも
#define cast(src, type) ((type)src)
で良いのだ