为什么在宏中使用看似毫无意义的 do-while 和 if-else 语句?

2022-01-30 00:00:00 c c++ c-preprocessor c++-faq

在许多 C/C++ 宏中,我看到宏的代码被包裹在一个看似毫无意义的 do while 循环中.以下是示例.

In many C/C++ macros I'm seeing the code of the macro wrapped in what seems like a meaningless do while loop. Here are examples.

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

我看不到 while 正在做什么.为什么不直接写这个呢?

I can't see what the do while is doing. Why not just write this without it?

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

推荐答案

do ... whileif ... else 可以做到一个宏后面的分号总是意味着同样的事情.比方说你有类似你的第二个宏的东西.

The do ... while and if ... else are there to make it so that a semicolon after your macro always means the same thing. Let's say you had something like your second macro.

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

现在,如果您要在 if ... else 语句中使用 BAR(X);,其中 if 语句的主体未包含在大括号中,你会得到一个糟糕的惊喜.

Now if you were to use BAR(X); in an if ... else statement, where the bodies of the if statement were not wrapped in curly brackets, you'd get a bad surprise.

if (corge)
  BAR(corge);
else
  gralt();

上面的代码会展开成

if (corge)
  f(corge); g(corge);
else
  gralt();

这在语法上是不正确的,因为 else 不再与 if 相关联.在宏中用大括号括起来并没有帮助,因为大括号后面的分号在语法上是不正确的.

which is syntactically incorrect, as the else is no longer associated with the if. It doesn't help to wrap things in curly braces within the macro, because a semicolon after the braces is syntactically incorrect.

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

有两种方法可以解决此问题.第一种是在宏中使用逗号对语句进行排序,而不会剥夺它像表达式一样的能力.

There are two ways of fixing the problem. The first is to use a comma to sequence statements within the macro without robbing it of its ability to act like an expression.

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

上面版本的bar BAR把上面的代码展开成下面的代码,语法正确.

The above version of bar BAR expands the above code into what follows, which is syntactically correct.

if (corge)
  f(corge), g(corge);
else
  gralt();

如果你有一个更复杂的代码体而不是 f(X) 需要放在它自己的块中,例如声明局部变量,这不起作用.在最一般的情况下,解决方案是使用像 do ... while 这样的东西来使宏成为一个使用分号而不会混淆的单个语句.

This doesn't work if instead of f(X) you have a more complicated body of code that needs to go in its own block, say for example to declare local variables. In the most general case the solution is to use something like do ... while to cause the macro to be a single statement that takes a semicolon without confusion.

#define BAR(X) do { 
  int i = f(X); 
  if (i > 4) g(i); 
} while (0)

你不必使用 do ... while,你也可以用 if ... else 做一些事情,虽然当 if... elseif ... else 内部展开,它会导致dangling else",这可能会使现有的 dangling else 问题更难找到,如下面的代码所示.

You don't have to use do ... while, you could cook up something with if ... else as well, although when if ... else expands inside of an if ... else it leads to a "dangling else", which could make an existing dangling else problem even harder to find, as in the following code.

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

关键是在悬挂分号错误的上下文中用完分号.当然,此时可以(并且可能应该)争辩说,将 BAR 声明为实际函数而不是宏会更好.

The point is to use up the semicolon in contexts where a dangling semicolon is erroneous. Of course, it could (and probably should) be argued at this point that it would be better to declare BAR as an actual function, not a macro.

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

In summary, the do ... while is there to work around the shortcomings of the C preprocessor. When those C style guides tell you to lay off the C preprocessor, this is the kind of thing they're worried about.

相关文章