C++ – 为什么在宏中使用明显无意义的do-while和if-else语句?

在许多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 ... whileif ... else在那里让这个后您的宏分号总是意味着同样的事情。假设你有类似你的第二个宏。

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

有两种方法可以解决问题。第一种是使用逗号来对宏中的语句进行排序,而不会使其具有像表达式一样的能力。

#define BAR(X) f(X), g(X)

上面的bar版本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导致“ 悬挂其他 ”,这可能使现有悬挂的其他问题更难找到,如下面的代码。

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();

关键是在悬挂分号错误的情况下用掉分号。当然,在这一点上它可以(并且可能应该)被认为最好将其声明BAR为实际函数,而不是宏。

总之,do ... while可以解决C预处理器的缺点。当那些C风格指南告诉你裁掉C预处理器时,这是他们担心的事情。


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

有三个好的“提示”可以成功:

帮助宏表现得像真正的代码

普通代码通常以分号结束。如果用户查看不需要的代码……

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 ++习语: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.
}

添加评论

友情链接:蝴蝶教程