せっかくの休みなのでw、printf() のような関数を自作してみた。

static char my_buffer[1024 * 1024];

extern void my_printf(const char *fmt,...);

訳あって、書き出し先は my_buffer という外部変数。va_list も訳あって使えないので、可変長引数は自前で読み込む。

可変長引数はどう読み込めば良いか? C 言語の場合、内部変数はスタックに積まれるため、後に宣言したものの方が若いアドレスになる。でも、引数の場合は違っていて、「引数1」の次のアドレスに「引数2」が存在することが保証されているらしい。

URL: http://networkprogramming.blog18.fc2.com/blog-entry-7.html

これを利用すれば、可変長引数を自前で読み込むのは簡単。fmt の中に %d や %x が見つかるたびに param32p を読み進めていけば良い。

void
my_printf(const char *fmt,...)
{
    unsigned long int *param32p = (unsigned long int *)&fmt + 1;

ところが!!試しに最適化レベルを 3 にしたら、「引数1」の 前の アドレスに「引数2」が存在していた。ガセだったか…。以下のページにも可変長引数を扱う時は stdarg.h 使えと書いてあった。va_list が使えないというのは勘違いな気がしてきたので、後で確認してみよう。

URL: http://docs.hp.com/ja/B2355-90858/stdarg.5.html

itoa() も使えないため自作した。「符号あり/なし 10 進数」と「符号なし 16 進数」をサポートしたが、意外に大変だった。一番時間かかったかも。

やっと printf() モドキが完成!!
と言いたいところだけど、"%d" の数と可変長引数の数が合ってなくても warning にならないのね。ちょっとヤダなあ…。

調べてみたら、gcc 系の場合以下のように宣言すれば良いことが分かった。

extern void my_printf(const char *fmt,...)
#ifdef __GNUC__
    __attribute__ ((format (printf, 1, 2)))
#endif  /* __GNUC__ */
    ;

attribute については、以下にある程度まとまっていた。いろいろあるなあ。。。
URL: http://sugarpot.sakura.ne.jp/yuno/?gcc%2Fattribute

追記(2009-03-21):
コメントにもありますが、最適化レベル 3 で動作が変わったのはインライン展開されていたためでした。インライン化できないような単純でない関数にしたり、attribute((noinline)) を付けたりすれば大丈夫そうですが、移植性が落ちるのでやはり避けた方が良いと思います。