多くの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ステートメントの本体が中括弧で囲まれていないif ... else
ステートメントでBAR(X);
を使用する場合、ひどい驚きがあります。
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)
上記のバーBAR
は、上記のコードを次のように展開します。これは構文的に正しいです。
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
の内部に展開すると、「- dangling else "。これにより、次のコードのように、既存のdangling 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++コンパイラは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)
の後の唯一の有効なトークンはセミコロンだからです。
コンパイラは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
にバインドされるため、ここでは実際には「ぶら下がるelse」問題はありません。)
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ループで宣言されているローカル変数を使用します。
したがって、マクロ内で一般的な変数名を使用しないでください。
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");
私はそれが言及されたとは思わないので、これを検討してください
while(i<100)
FOO(i++);
に翻訳されます
while(i<100)
do { f(i++); g(i++); } while (0)
マクロによってi++
が2回評価される様子に注目してください。これはいくつかの興味深いエラーにつながる可能性があります。
このトリックは、あなたが特定の値を順番に処理しなければならないような状況で非常に役立つことがわかりました。各処理レベルで、何らかのエラーや無効な状態が発生した場合は、それ以上の処理を避けて早めに処理を進めることができます。例えば.
#define CALL_AND_RETURN(x) if ( x() == false) break;
do {
CALL_AND_RETURN(process_first);
CALL_AND_RETURN(process_second);
CALL_AND_RETURN(process_third);
//(simply add other calls here)
} while (0);
do {} while (0)
がif (1) {}
よりも使用されるのは、マクロdo {} while (0)
を呼び出して他の種類のブロックにする前に誰かがコードを変更することができないからです。たとえば、if (1) {}
で囲まれたマクロを次のように呼び出したとします。
else
MACRO(x);
それは実際にはelse if
です。微妙な違い