例えば、 C/C++ で開発を行っている現場であれば、 以下のようなマクロが1つや2つや3つや4つや N 個は有るのではないでしょうか。
#ifdef DEBUG
extern int debug_level;
/* 閾値以下のレベル値の場合に、式 exp を評価するマクロ */
#define AT_DEBUG(level, exp) \
if(level <= debug_level){ exp; }
#else
/* DEBUG マクロが定義されていない場合はコードを丸ごと除外 */
#define AT_DEBUG(level, exp) /* nop */
#endif
AT_DEBUG() の定義 (1)このマクロは、以下のようにして使用します。
value = get_value();
AT_DEBUG(1, printf("value=%s", value));
AT_DEBUG() の使用人によっては、
AT_DEBUG()
マクロを以下のように定義する人がいるかもしれません。
#define AT_DEBUG(level, exp) \
if(level <= debug_level){ exp; }else
AT_DEBUG() の定義 (2)定義 (2) のような書き方をする人は、 if/else 記述にブロックステートメントを使用しない、 古くからのカーネルソースやフリーソフトウェアでの実装機会が、 多かったのではないでしょうか。
例えば、以下のソースを対象として考えて見ましょう。
if(condA)
AT_DEBUG(1, printf("condA is true"));
else
statementB;
定義 (1) の AT_DEBUG() の場合、
上記のソースはプリプロセス後に以下のように展開されます。
if(condA)
if(1 <= debug_level){ printf("condA is true"); };
else
statementB;
以上のソースを(狭義の) C コンパイラが解釈した場合、
if(condA) から始まる if ステートメントは、
元ソースの AT_DEBUG()
末尾に書かれたセミコロンによって終了させられてしまいます。
結果として、それに続く else 節は「if ステートメントを持たない」
不正な記述とみなされるか、
「さらに上位の if ステートメントの一部」とみなされますので、
いずれにしても誤って解釈されることになります。
かと言って、
「このような場合にのみ AT_DEBUG()
末尾のセミコロンを省略」したのでは、
記述が統一されません
(ついでに Emacs による整形等が上手く機能しなくなります)。
つまり、
定義 (1) の AT_DEBUG() は、
if/else での記述にブロックステートメントを使用する今時の記述では、
一見問題の無いように見えますが、
旧形式の実装との混在を考えた場合、
必ずしも妥当ではないと言えます
ここで定義 (2) の AT_DEBUG() を用いた場合:
if(condA)
if(1 <= debug_level){ printf("condA is true"); }else;
else
statementB;
AT_DEBUG()
末尾のセミコロンは「直前の else 節を終了させるもの」とみなされるため、
if(condA) から始まる if ステートメントは、
元ソースにおける else 節まで継続します。
これにより、
期待するコンパイル結果を得ることができます。
このような状況では有効な定義 (2) の AT_DEBUG() ですが、
以下のような場合には、
原因を特定し難い障害の原因と成り得る問題を抱えています。
statementA;
AT_DEBUG(1, printf("statementA is passed"))
statementB;
上記のソースで注意して欲しいのは、
AT_DEBUG() 末尾のセミコロンを忘れている、
という、
非常にありふれて且つ気付きにくい間違いを犯している点です。
定義 (1) の AT_DEBUG 定義は、
元々末尾がブロックステートメントなので、
実装した際の意図に沿った解釈がなされます
(「セミコロンを書き忘れていてもコンパイルが通ってしまう」というのは、
別な意味で問題ではあるのですが…)。
その一方で、
定義 (2) の AT_DEBUG() の場合、
このソースがプリプロセッサによって展開されると以下のようになります
(こちらもコンパイルは通ります)。
statementA;
if(1 <= debug_level){ printf("statementA is passed"); }else
statementB;
マクロ展開後のソースでは、
statementB が
AT_DEBUG() 末尾の else 節の一部とみなされるため、
実装者が意図したものと全く異なる解釈がなされてしまいます。
歴史的諸々の経緯(記述量削減等)があったのでしょうが、 どこかの時点で、 「if/else 等の制御構文にはブロックステートメント以外記述できない」 ぐらいの大鉈を振るっても良かったのではないかと思います (C 言語の普及度・重要度がそれをさせなかったのでしょうね)。
話題を AT_DEBUG() に戻しますが、
結局のところ、
妥当な定義は以下のような感じでしょうか。
#define AT_DEBUG(level, exp) \
((debug_level < (level)) || ((exp), 0))
AT_DEBUG() 定義このように定義することで、
AT_DEBUG() 自体は「式」と等価になりますから、
前述したような記述位置に関わる問題は発生しません。
また、セミコロンの記述忘れに際しても、
常にコンパイルエラーが検出されます。
"(exp, 0)" 記述は、
exp が戻り値 void の関数だった場合に、
上記マクロを「式」として成立させる(= 値を持たせる)
ためのものです。
これは「コンマ演算」(by K&R)と呼ばれるもので、
相当 C 言語を使い込んでいる人でないと知らない可能性が高いですが、
ユーティリティマクロ定義等では結構重宝しますので、
覚えておいて損はありません。
なお、上記のマクロ定義は 「条件評価は左から」という前提を元に書かれているため、 「|| や && で結合された式の評価順序はコンパイラの実装依存」 という C 言語の仕様から見たら、 建前上は安全性の面で完全ではありません。
所謂 "else" に当たる部分の記述が 「気分的に」冗長だったので採用しなかったのですが、 言語仕様上から見ても安全な側に振っておきたいのであれば、 以下のような記述が良いでしょう。
#define AT_DEBUG(level, exp) \
(((level) <= debug_level) ? ((exp), 0) : 0)
AT_DEBUG() 定義