数値の文字列化

デバッグ文を埋め込むとき、


ほげ発生 at function_x:n
みたいに、「関数名:行番号」を付加したいと思うことはよくある。

GCCには__FUNCTION__という組み込みマクロがあり、Linuxでは__FUNC__という名前で定義されている。これがこの用途に使えるのだが標準化されておらず、他のコンパイラVC++など)では使えない。そんなときは仕方なく


#ifndef __FUNC__
#define __FILE__
#endif
という感じで代替しておく。

で、先ほどのデバッグ文だが、いちいち


debug_printf("fatal error at %s, %d", __FUNC__, __LINE__)
とは書きたくない。
debug_printf("fatal error")
と書いたらat以下を付加してくれるのがよい。

まず考えるのは、以下のように文字列と数値を関数_debug_printfに渡し、_debug_printf内でメッセージを生成するやり方。


void _debug_printf(const char *func, int line, const char *msg);
#define debug_printf(msg) _debug_printf(__FUNC__, __LINE__, msg)
普通はこれでいいが、引数を一つにしたい場合もある。

行番号は数値なので、文字列化しないと連結できない。そこで文字列化演算子#を使い、


void _debug_printf(const char *);
#define num_to_str(num) #num
#define debug_printf(msg) \
_debug_printf(msg " at " __FUNC__ ":" num_to_str(__LINE__))
としてみるが、これはうまくいかない。


ほげ発生 at function_x:__LINE__
となってしまう。これはGCCでもVC++でも同様の結果となる。

解決策は、


void _debug_printf(const char *);
#define _num_to_str(num) #num
#define num_to_str(num) _num_to_str(num)
#define debug_printf(msg) \
_debug_printf(msg " at " __FUNC__ ":" num_to_str(__LINE__))
のように、ダミーのマクロを通せばよい。

なぜこうなるのか、プリプロセッサの動作は想像するしかないが、文字列化の識別子の優先度は高いようだ。
最初の例では、num_to_strが文字列化の識別子として働いているので、プリプロセスの1パス目で引数__LINE__が問答無用で文字列化されてしまう。
後の例では、1パス目ではnum_to_strは_num_to_strに置換されるのみで、_num_to_strの評価は2パス目に回される。一方、num_to_strの引数である__LINE__も1パス目で評価され、行番号に置換される。それにより2パス目で期待の結果が得られる。
__LINE__ではないが、この文字列化のテクニックはLinuxカーネルヘッダでも使われていて、「some cpp magic」などと表現されている。

しかしVC++では、__LINE__以外はうまくいくが、__LINE__の場合は、


ほげ発生 at function_x:(__LINE__Var+番号)
となってしまう。なんじゃこりゃ。
__LINE__Varでググったところ、/ZIオプションを付加した場合(デバッグ情報が「エディット・コンティニュー用のプログラム データベース」の場合)にこうなるとのこと。
「プログラム データベースを使用」に変更してみたところ、確かに直った。

エディット・コンティニューというのは、デバッグ中にコードを編集できるというすごい仕組みである。これも想像だが、編集すると行番号が変わるので、__LINE__はリファレンス形式にしておいて、実行するたびに実際の値に書き換えているのではないか。「(__LINE__Var+番号)」の番号のところは通し番号となっているから、これは参照テーブルの番号なのだろう。
だから__LINE__を文字列化してしまうと、あらぬところを書き換えているのかもしれない。