マクロで明らかに無意味な do-while および if-else ステートメントを使用するのはなぜですか?

do ... whileif ... else マクロの後のアセミコロンが常に同じことを意味するようにするためにあります。 2 番目のマクロのようなものがあるとしましょう。

#define BAR(X) f(x); g(x)

BAR(X); を使用する場合 if ... else で if ステートメントの本体が中かっこで囲まれていないステートメントを使用すると、驚くべきことになります。

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 内で展開します これにより、次のコードのように、既存のダングリング 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) ;

有効なコード 2 を生成

マクロが次のような場合:

#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() が常に呼び出されるという意図しない結果が生じる可能性があります。