为什么使用显然毫无意义的做法

在很多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 ... 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();

有两种方法可以解决这个问题。 第一种方法是使用逗号排列宏中的语句,但不能使其像表达式那样操作。

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

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

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

if (test) FOO( baz);

扩展到:

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

这在语法上是正确的,所以没有编译器错误,但可能会有非预期的后果,即g()将始终被调用。

链接地址: http://www.djcxy.com/p/6197.html

上一篇: Why use apparently meaningless do

下一篇: statement and bitwise operations same in this example?