printfデバッグに便利なファイル行数表示マクロ

emacsgccを普段使っているレガシープログラマの自分としては、デバッグはprintfが一番便利だと思っている。まれにemacsの中でgdbも使うが、必要以上に時間がかかるし、視野が狭くなり袋小路に陥りやすい。そもそも組込み系ではデバッグ環境が提供されていない場合もある。そんなときは、はやりprintfが手っ取り早くて確実だ。その際、printf出力にファイル名と行数を表示する自前のマクロを長年使ってきた。

printf(HERE "x=%d\n", x);

のような感じだ。HEREがそのマクロでファイル名と行数の文字列として"test.cpp(68)"のように展開される。簡単で便利なので自分は非常に気に入って使っている。実際には、このHEREを使ってもっと簡潔なデバッグprint文マクロを使っているが、周囲をみるとほとんどの場合、

printf("%s(%d) x=%d\n", __FILE__, __LINE__, x);

のような書き方をしている。printfデバッグはコード中に簡単に追加できて、必要なくなったらすぐに消せるから便利なのに、いちいちこのように書いていたのでは面倒だろうと、HEREマクロを見せたところ、そんな書き方ができるのかと非常に驚かれた。そこには、ある程度経験を積んだプログラマでもその機能を知らないか、すぐには思いつかないパターンがあるようである。

1つめは、文字列と文字列を続けて記述(途中に空白や改行があってもよい)すると一つの連続した文字列になる、というC言語の仕様である。

2つめは、マクロ引数を文字列にするという#記法である。

3つめは、マクロ引数の展開に関する規則である。

詳しく説明しよう。HEREというマクロが"test.cpp(68)"という文字列に展開されても、

printf("%s x=%d\n", HERE, x);

などと記述しなければならないとしたら面倒である。HEREは文字列定数に展開されるから、%sでフォーマット表示する必要はなく、最初に書いたようにフォーマット文字列の前に連続して記述すればHEREの文字列と"x=%d"の文字列が連結されて1つの文字列になる。この空白文字で連結された複数の文字列リテラルは1つの文字列リテラルと等価であるという文法規則は非常に便利である。JavaScriptでも使えたらよいのにといつも思うが残念ながらそれはできない。C/C++言語以外にこのような文法のある言語は思い浮かばない。そのためか、Cのプログラマでも知らないという人が結構いる。

次に、__LINE__というファイルの行数としての数値に展開されるマクロと同等のものを数値ではなく文字列に展開するマクロを定義するにはどうすればよいかであるが、もしそのようなマクロが__SLINE__で定義されるとすると、HEREマクロは以下のようにできる。

#define HERE        __FILE__ "(" __SLINE__ ")"

マクロ引数を文字列化するマクロ _STRを下記のように定義する。

#define  _STR(x)    #x

すると、__SLINE__は次のように定義すればできそうだが、そうはならない。

#define __SLINE__   _STR(__LINE__)

この場合、__SLINE__は単に"__LINE__"という文字列に置き換わるだけだ。どうすればよいか。それにはマクロの中で利用できる#の機能をちゃんと理解する必要がある。#Lispで言えばquoteのようなもので、中身を実際の値に置き換えることはしないのである。どういうことか例で示そう。

#define LINE       123
#define TEST(x)    (#x, x)

というマクロLINEとTESTを定義してTEST(LINE)を展開すると、("LINE", 123)に展開されるのである。ならば、上記_STRマクロには__LINE__そのものではなく、__LINE__が展開された数値を渡すようにすればよい。そのためのマクロ_STR2を以下のように定義する。

#define _STR2(x)    _STR(x)

そうすると、_STR2(__LINE__)は希望通り、"123"のような数値文字列に展開される。

結局、HEREマクロは以下のように定義できる。

#define _STR(x)      #x
#define _STR2(x)     _STR(x)
#define __SLINE__    _STR2(__LINE__)
#define HERE         __FILE__ "(" __SLINE__ ")"

もちろん、途中の__SLINE__は必要性がなければ、定義しないで直接置き換えてもよい。

C言語のマクロの利用についてはいろいろ賛否両論議論があるが、##記法もあわせ、このような使い方は是非マスターしておきたいものである。