多くの C/C++ マクロで、マクロのコードが無意味なdo while
ループに包まれているのを目にします。以下に例を示します。
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
が何をしているのか見えませんdo while
。それなしでこれを書いてみませんか?
#define FOO(X) f(X); g(X)
多くの C/C++ マクロで、マクロのコードが無意味なdo while
ループに包まれているのを目にします。以下に例を示します。
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
が何をしているのか見えませんdo while
。それなしでこれを書いてみませんか?
#define FOO(X) f(X); g(X)
do ... while
とはif ... else
、マクロの後のセミコロンが常に同じことを意味するようにするためにあります。2 番目のマクロのようなものがあったとします。
#define BAR(X) f(x); g(x)
ここで、if ステートメントの本体が中括弧で囲まれていないステートメントで使用するとしたら、驚くべきことですBAR(X);
。if ... else
if (corge)
BAR(corge);
else
gralt();
上記のコードは次のように展開されます
if (corge)
f(corge); g(corge);
else
gralt();
これは、else が if に関連付けられていないため、構文的に正しくありません。中括弧の後のセミコロンは構文的に正しくないため、マクロ内で中括弧で囲むことは役に立ちません。
if (corge)
{f(corge); g(corge);};
else
gralt();
問題を解決するには 2 つの方法があります。1 つ目は、式のように振る舞う能力を奪うことなく、マクロ内でステートメントを連続させるためにコンマを使用することです。
#define BAR(X) f(X), g(X)
上記のバージョンの barBAR
は、上記のコードを次のように展開します。これは構文的に正しいものです。
if (corge)
f(corge), g(corge);
else
gralt();
f(X)
たとえば、ローカル変数を宣言するなど、独自のブロックに入れる必要があるより複雑なコード本体がある場合、これは機能しません。最も一般的な場合の解決策はdo ... while
、マクロを混乱なくセミコロンを取る単一のステートメントにするようなものを使用することです。
#define BAR(X) do { \
int i = f(X); \
if (i > 4) g(i); \
} while (0)
を使用する必要はありません。 を使用do ... while
して何かを調理することもできますが、if ... else
のif ... else
内部で展開するif ... else
と「ダングリング else」が発生し、次のコードのように、既存のダングリング else の問題がさらに見つけにくくなる可能性があります。 .
if (corge)
if (1) { f(corge); g(corge); } else;
else
gralt();
ポイントは、ぶら下がっているセミコロンが間違っているコンテキストでセミコロンを使い切ることです。もちろん、この時点でBAR
、マクロではなく実際の関数として宣言する方がよいと主張することができます (おそらくそうすべきです)。
要約するdo ... while
と、C プリプロセッサの欠点を回避するためにあります。それらの C スタイル ガイドが C プリプロセッサを解雇するように言うとき、これは彼らが心配していることです。
マクロは、プリプロセッサが本物のコードに挿入するテキストのコピー/貼り付けです。マクロの作成者は、置換によって有効なコードが生成されることを望んでいます。
それを成功させるための 3 つの良い「ヒント」があります。
通常のコードは通常、セミコロンで終了します。ユーザーはコードを必要としないコードを表示する必要があります...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
これは、セミコロンがない場合、コンパイラがエラーを生成することをユーザーが予期していることを意味します。
しかし、真に正当な理由は、いつかマクロの作成者がマクロを本物の関数 (おそらくインライン化された関数) に置き換える必要があるからです。したがって、マクロは実際にそのように動作する必要があります。
したがって、セミコロンが必要なマクロが必要です。
jfm3 の回答に示されているように、マクロに複数の命令が含まれている場合があります。また、マクロが if ステートメント内で使用されている場合、これは問題になります。
if(bIsOk)
MY_MACRO(42) ;
このマクロは次のように展開できます。
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
g
関数は の値に関係なく実行されますbIsOk
。
これは、マクロにスコープを追加する必要があることを意味します。
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
マクロが次のような場合:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
次のコードでは、別の問題が発生する可能性があります。
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
次のように展開されるためです。
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
もちろん、このコードはコンパイルされません。繰り返しになりますが、ソリューションはスコープを使用しています。
#define MY_MACRO(x) { int i = x + 1 ; f(i) ; }
void doSomething()
{
int i = 25 ;
{ int i = 32 + 1 ; f(i) ; } ; // was MY_MACRO(32) ;
}
コードは再び正しく動作します。
この効果を生み出す C/C++ の慣用句が 1 つあります。 do/while ループ:
do
{
// code
}
while(false) ;
do/while はスコープを作成して、マクロのコードをカプセル化し、最後にセミコロンを必要とするため、必要なコードに展開できます。
ボーナス?
C++ コンパイラは、事後条件が false であるという事実がコンパイル時に認識されるため、do/while ループを最適化して取り除きます。これは、次のようなマクロを意味します。
#define MY_MACRO(x) \
do \
{ \
const int i = x + 1 ; \
f(i) ; g(i) ; \
} \
while(false)
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
MY_MACRO(42) ;
// Etc.
}
として正しく展開されます
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
do
{
const int i = 42 + 1 ; // was MY_MACRO(42) ;
f(i) ; g(i) ;
}
while(false) ;
// Etc.
}
そして、次のようにコンパイルおよび最適化されます。
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
@ jfm3 - 質問に対する素晴らしい答えがあります。また、単純な 'if' ステートメントを使用して、(エラーがないため) より危険な可能性のある意図しない動作をマクロのイディオムが防止することを追加することもできます。
#define FOO(x) f(x); g(x)
if (test) FOO( baz);
次のように展開します。
if (test) f(baz); g(baz);
これは構文的に正しいため、コンパイラ エラーは発生しませんが、g() が常に呼び出されるという意図しない結果が生じる可能性があります。
上記の回答は、これらの構成要素の意味を説明していますが、言及されていない 2 つの重要な違いがあります。do ... while
実際、if ... else
構文よりも を好む理由があります。
この構文の問題は、セミコロンを入れる必要if ... else
がないことです。このコードのように:
FOO(1)
printf("abc");
(誤って) セミコロンを省略しましたが、コードは次のように展開されます。
if (1) { f(X); g(X); } else
printf("abc");
サイレントにコンパイルされます (ただし、一部のコンパイラは、到達できないコードに対して警告を発する場合があります)。しかし、printf
ステートメントは決して実行されません。
do ... while
while(0)
の後の唯一の有効なトークンはセミコロンであるため、construct にはこのような問題はありません。
do {} while (0)
if (1) {} else
マクロが 1 つの命令のみに展開されるようにします。さもないと:
if (something)
FOO(X);
次のように展開されます。
if (something)
f(X); g(X);
そして、制御ステートメントg(X)
の外で実行されます。これは、 と を使用するとif
回避されます。do {} while (0)
if (1) {} else
GNUステートメント式(標準 C の一部ではない)を使用すると、単純に を使用してこれを解決するよりも優れた方法がdo {} while (0)
あります。if (1) {} else
({})
#define FOO(X) ({f(X); g(X);})
do {} while (0)
また、この構文は、次のように戻り値と互換性があります (互換性がないことに注意してください)。
return FOO("X");
コンパイラがループを最適化することが期待されていdo { ... } while(false);
ますが、その構造を必要としない別の解決策があります。解決策は、コンマ演算子を使用することです。
#define FOO(X) (f(X),g(X))
またはさらにエキゾチックに:
#define FOO(X) g((f(X),(X)))
これは個別の命令ではうまく機能しますが、変数が構成され、の一部として使用される場合には機能しません#define
。
#define FOO(X) (int s=5,f((X)+s),g((X)+s))
これを使用すると、do/while 構造を使用することが強制されます。
Jens Gustedt のP99 プリプロセッサ ライブラリ(そうです、そのようなものが存在するという事実も私を驚かせました!) はif(1) { ... } else
、次のように定義することにより、小さいながらも重要な方法で構成を改善します。
#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP
これの論理的根拠は、構文とは異なり、do { ... } while(0)
指定されたブロック内で引き続き機能しbreak
ますが、マクロ呼び出しの後にセミコロンが省略されていると構文エラーが発生し、次のブロックがスキップされるためです。(は、マクロ内の 最も近い にバインドされるため、実際には「他にぶら下がっている」問題はありません。)continue
((void)0)
else
if
C プリプロセッサを使用して多かれ少なかれ安全に実行できる種類の処理に興味がある場合は、そのライブラリを調べてください。
いくつかの理由で、最初の回答にはコメントできません...
ローカル変数を使ったマクロを見せてくれた人もいましたが、マクロで任意の名前を使用できないとは誰も言いませんでした! それはいつかユーザーを噛むでしょう!なんで?入力引数がマクロ テンプレートに代入されるためです。そして、マクロの例では、おそらく最も一般的に使用される変数名iを使用しています。
たとえば、次のマクロの場合
#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)
次の関数で使用されます
void some_func(void) {
int i;
for (i = 0; i < 10; ++i)
FOO(i);
}
マクロは、some_func の先頭で宣言されている目的の変数 i を使用しませんが、マクロの do ... while ループで宣言されているローカル変数を使用します。
したがって、マクロで一般的な変数名を使用しないでください。
私はそれが言及されたとは思わないので、これを考慮してください
while(i<100)
FOO(i++);
に翻訳されます
while(i<100)
do { f(i++); g(i++); } while (0)
i++
マクロによって2回評価されることに注意してください。これにより、いくつかの興味深いエラーが発生する可能性があります。