在许多C/ c++宏中,我看到宏的代码被包装在似乎毫无意义的do while循环中。下面是一些例子。

#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else

我看不出他在做什么。为什么不干脆不写呢?

#define FOO(X) f(X); g(X)

当前回答

宏是预处理器将放入真正代码中的文本片段的复制/粘贴;宏的作者希望替换将产生有效的代码。

这里有三个成功的“秘诀”:

帮助宏像真正的代码一样运行

普通代码通常以分号结束。如果用户视图代码不需要一个…

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) ;

不管bIsOk的值是多少,函数g都会被执行。

这意味着我们必须给宏添加一个作用域:

#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++习惯用法可以产生这种效果:do/while循环:

do
{
    // code
}
while(false) ;

do/while可以创建一个作用域,从而封装宏的代码,最后需要一个分号,从而展开为需要分号的代码。

奖金吗?

c++编译器会优化掉do/while循环,因为它的后置条件为false的事实在编译时就已经知道了。这意味着像这样的宏:

#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.
}

其他回答

解释

执行{}while(0)和if (1) {} else确保宏被扩展到只有一条指令。否则:

if (something)
  FOO(X); 

将扩大为:

if (something)
  f(X); g(X); 

g(X)将在if控制语句之外执行。当使用do {} while(0)和if (1) {} else时,可以避免这种情况。


更好的选择

使用GNU语句表达式(不是标准C的一部分),你有一个比{}while(0)和if (1) {} else更好的方法来解决这个问题,只需使用({}):

#define FOO(X) ({f(X); g(X);})

并且此语法与返回值兼容(注意do {} while(0)不兼容),例如:

return FOO("X");

@jfm3 -你对这个问题的回答很好。你可能还想补充一点,宏习语还可以通过简单的'if'语句防止可能更危险的(因为没有错误)意外行为:

#define FOO(x)  f(x); g(x)

if (test) FOO( baz);

扩展:

if (test) f(baz); g(baz);

这在语法上是正确的,因此没有编译器错误,但可能会产生意想不到的后果,即总是会调用g()。

宏是预处理器将放入真正代码中的文本片段的复制/粘贴;宏的作者希望替换将产生有效的代码。

这里有三个成功的“秘诀”:

帮助宏像真正的代码一样运行

普通代码通常以分号结束。如果用户视图代码不需要一个…

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) ;

不管bIsOk的值是多少,函数g都会被执行。

这意味着我们必须给宏添加一个作用域:

#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++习惯用法可以产生这种效果:do/while循环:

do
{
    // code
}
while(false) ;

do/while可以创建一个作用域,从而封装宏的代码,最后需要一个分号,从而展开为需要分号的代码。

奖金吗?

c++编译器会优化掉do/while循环,因为它的后置条件为false的事实在编译时就已经知道了。这意味着像这样的宏:

#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.
}

虽然编译器应该优化掉do{…},(假);循环,还有另一个解决方案不需要这个结构。解决方案是使用逗号操作符:

#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结构。

我想没有提到过,所以考虑一下

while(i<100)
  FOO(i++);

会被翻译成

while(i<100)
  do { f(i++); g(i++); } while (0)

注意i++是如何被宏求值两次的。这可能会导致一些有趣的错误。