C++ 为什么在宏中使用看起来毫无意义的do while和if else语句?

C++ 为什么在宏中使用看起来毫无意义的do while和if else语句?,c++,c,c-preprocessor,c++-faq,C++,C,C Preprocessor,C++ Faq,在许多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) do。。。而和如果。。。否则会使 宏后面的分号总是表示相同的内容。比如说你 有点像你的第二个宏 #define BAR(X) f

在许多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)

do。。。而
如果。。。否则
会使 宏后面的分号总是表示相同的内容。比如说你 有点像你的第二个宏

#define BAR(X) f(x); g(x)
现在,如果要使用
BAR(X)
语句,如果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。。。而
则使宏成为一条带分号的语句,而不会混淆

#define BAR(X) do { \
  int i = f(X); \
  if (i > 4) g(i); \
} while (0)
您不必使用
do。。。而
,如果。。。else也一样,尽管当
如果。。。else
中展开,如果。。。else
它会导致一个“”,这可能会使现有的悬空else问题更难找到,如下面的代码所示

if (corge)
  if (1) { f(corge); g(corge); } else;
else
  gralt();
关键是在悬空分号错误的上下文中使用分号。当然,在这一点上可以(也可能应该)认为,最好将
BAR
声明为实际函数,而不是宏


总之,
do。。。而
则是为了解决C预处理器的缺点。当那些C风格的指南告诉你停止C预处理器时,这正是他们担心的事情。

@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) ;
此宏可以展开为:

#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(i<100)
  FOO(i++);

while(i虽然预期编译器会优化掉
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))

由于某些原因,我无法对第一个答案发表评论

你们中的一些人展示了带有局部变量的宏,但没有人提到不能在宏中使用任何名称!总有一天它会咬到用户!为什么?因为输入参数被替换到宏模板中。在宏示例中,您使用的可能是最常用的变量名称i

例如,当下列宏

#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)
#定义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);
}
void一些函数(void){
int i;
对于(i=0;i<10;++i)
富(i),;
}
宏不会使用在某些函数开头声明的预期变量i,而是使用在宏的do…while循环中声明的局部变量


因此,千万不要在宏中使用通用变量名!

上述答案解释了这些结构的含义,但这两个结构之间有一个没有提到的显著差异。事实上,有理由更喜欢
do…while
而不是
if…else
结构

if…else
构造的问题在于它不会强制您将
#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);
}
FOO(1)
printf("abc");
if (1) { f(X); g(X); } else
printf("abc");
if (something)
  FOO(X); 
if (something)
  f(X); g(X); 
#define FOO(X) ({f(X); g(X);})
return FOO("X");