C++ 为什么在宏中使用显然毫无意义的 do-while 和 if-else 语句?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/154136/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why use apparently meaningless do-while and if-else statements in macros?
提问by jfm3
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.
在许多 C/C++ 宏中,我看到宏的代码被包裹在一个看起来毫无意义的do while
循环中。以下是示例。
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
I can't see what the do while
is doing. Why not just write this without it?
我看不到do while
它在做什么。为什么不写这个没有它?
#define FOO(X) f(X); g(X)
回答by jfm3
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.
该do ... while
和if ... else
在那里让这个宏后分号总是意味着同样的事情。假设你有类似你的第二个宏的东西。
#define BAR(X) f(x); g(x)
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.
现在,如果您要BAR(X);
在if ... else
语句中使用,其中 if 语句的主体没有用大括号括起来,您会感到意外。
if (corge)
BAR(corge);
else
gralt();
The above code would expand into
上面的代码将扩展为
if (corge)
f(corge); g(corge);
else
gralt();
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.
这在语法上是不正确的,因为 else 不再与 if 相关联。在宏中用大括号括起来无济于事,因为大括号后面的分号在语法上是不正确的。
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)
The above version of bar BAR
expands the above code into what follows, which is syntactically correct.
bar 的上述版本BAR
将上述代码扩展为以下内容,在语法上是正确的。
if (corge)
f(corge), g(corge);
else
gralt();
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.
如果f(X)
您有更复杂的代码体需要放入自己的块中,例如声明局部变量,则这不起作用。在最一般的情况下,解决方案是使用类似的do ... while
方法使宏成为单个语句,该语句采用分号而不会引起混淆。
#define BAR(X) do { \
int i = f(X); \
if (i > 4) g(i); \
} while (0)
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.
您不必使用do ... while
,您也可以使用来制作一些东西if ... else
,尽管在 aif ... else
内部扩展时会if ... else
导致“悬挂 else”,这可能会使现有的悬挂 else 问题更难找到,如下面的代码所示.
if (corge)
if (1) { f(corge); g(corge); } else;
else
gralt();
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.
关键是在悬空分号是错误的上下文中用完分号。当然,此时可以(并且可能应该)争论说,最好将其声明BAR
为实际函数,而不是宏。
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.
总之,do ... while
可以解决 C 预处理器的缺点。当那些 C 风格指南告诉你放弃 C 预处理器时,这就是他们担心的事情。
回答by paercebal
Macros are copy/pasted pieces of text the pre-processor will put in the genuine code; the macro's author hopes the replacement will produce valid code.
宏是预处理器将放入真正代码中的复制/粘贴文本;宏的作者希望替换能产生有效的代码。
There are three good "tips" to succeed in that:
有三个很好的“技巧”可以在这方面取得成功:
Help the macro behave like genuine code
帮助宏表现得像真正的代码
Normal code is usually ended by a semi-colon. Should the user view code not needing one...
普通代码通常以分号结束。如果用户查看代码不需要一个...
doSomething(1) ;
DO_SOMETHING_ELSE(2) // <== Hey? What's this?
doSomethingElseAgain(3) ;
This means the user expects the compiler to produce an error if the semi-colon is absent.
这意味着如果分号不存在,用户希望编译器产生错误。
But the real real good reason is that at some time, the macro's author will perhaps need to replace the macro with a genuine function (perhaps inlined). So the macro should reallybehave like one.
但真正好的理由是,在某个时候,宏的作者可能需要用真正的函数(可能是内联的)替换宏。所以宏应该真的表现得像一个。
So we should have a macro needing semi-colon.
所以我们应该有一个需要分号的宏。
Produce a valid code
生成有效代码
As shown in jfm3's answer, sometimes the macro contains more than one instruction. And if the macro is used inside a if statement, this will be problematic:
如 jfm3 的回答所示,有时宏包含多个指令。如果在 if 语句中使用宏,这将是有问题的:
if(bIsOk)
MY_MACRO(42) ;
This macro could be expanded as:
这个宏可以扩展为:
#define MY_MACRO(x) f(x) ; g(x)
if(bIsOk)
f(42) ; g(42) ; // was MY_MACRO(42) ;
The g
function will be executed regardless of the value of bIsOk
.
g
无论 的值如何,该函数都将被执行bIsOk
。
This means that we must have to add a scope to the macro:
这意味着我们必须为宏添加一个作用域:
#define MY_MACRO(x) { f(x) ; g(x) ; }
if(bIsOk)
{ f(42) ; g(42) ; } ; // was MY_MACRO(42) ;
Produce a valid code 2
产生一个有效的代码 2
If the macro is something like:
如果宏是这样的:
#define MY_MACRO(x) int i = x + 1 ; f(i) ;
We could have another problem in the following code:
我们可能在以下代码中遇到另一个问题:
void doSomething()
{
int i = 25 ;
MY_MACRO(32) ;
}
Because it would expand as:
因为它会扩展为:
void doSomething()
{
int i = 25 ;
int i = 32 + 1 ; f(i) ; ; // was MY_MACRO(32) ;
}
This code won't compile, of course. So, again, the solution is using a scope:
当然,这段代码不会编译。所以,同样,解决方案是使用范围:
#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) ;
}
The code behaves correctly again.
代码再次正确运行。
Combining semi-colon + scope effects?
结合分号+范围效果?
There is one C/C++ idiom that produces this effect: The do/while loop:
有一个 C/C++ 习惯用法可以产生这种效果: do/while 循环:
do
{
// code
}
while(false) ;
The do/while can create a scope, thus encapsulating the macro's code, and needs a semi-colon in the end, thus expanding into code needing one.
do/while 可以创建一个作用域,从而封装宏的代码,最后需要一个分号,从而扩展为需要一个的代码。
The bonus?
奖金?
The C++ compiler will optimize away the do/while loop, as the fact its post-condition is false is known at compile time. This means that a macro like:
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.
}
will expand correctly as
将正确扩展为
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.
}
and is then compiled and optimized away as
然后被编译和优化掉
void doSomething(bool bIsOk)
{
int i = 25 ;
if(bIsOk)
{
f(43) ; g(43) ;
}
// Etc.
}
回答by Michael Burr
@jfm3 - You have a nice answer to the question. You might also want to add that the macro idiom also prevents the possibly more dangerous (because there's no error) unintended behavior with simple 'if' statements:
@jfm3 - 你对这个问题有一个很好的回答。您可能还想补充一点,宏习语还可以通过简单的“if”语句防止可能更危险(因为没有错误)的意外行为:
#define FOO(x) f(x); g(x)
if (test) FOO( baz);
expands to:
扩展为:
if (test) f(baz); g(baz);
which is syntactically correct so there's no compiler error, but has the probably unintended consequence that g() will always be called.
这在语法上是正确的,因此没有编译器错误,但可能会产生意想不到的后果,即 g() 将始终被调用。
回答by ybungalobill
The above answers explain the meaning of these constructs, but there is a significant difference between the two that was not mentioned. In fact, there is a reason to prefer the do ... while
to the if ... else
construct.
上面的答案解释了这些结构的含义,但两者之间存在未提及的显着差异。其实,还有一个原因,更喜欢do ... while
到if ... else
结构。
The problem of the if ... else
construct is that it does not forceyou to put the semicolon. Like in this code:
if ... else
构造的问题在于它不会强迫您输入分号。就像在这段代码中:
FOO(1)
printf("abc");
Although we left out the semicolon (by mistake), the code will expand to
尽管我们(错误地)省略了分号,但代码将扩展为
if (1) { f(X); g(X); } else
printf("abc");
and will silently compile (although some compilers may issue a warning for unreachable code). But the printf
statement will never be executed.
并将静默编译(尽管某些编译器可能会针对无法访问的代码发出警告)。但是该printf
语句永远不会被执行。
do ... while
construct does not have such problem, since the only valid token after the while(0)
is a semicolon.
do ... while
构造没有这样的问题,因为 之后的唯一有效标记while(0)
是分号。
回答by Marius
While it is expected that compilers optimize away the do { ... } while(false);
loops, there is another solution which would not require that construct. The solution is to use the comma operator:
虽然预计编译器会优化掉do { ... } while(false);
循环,但还有另一种解决方案不需要该构造。解决方案是使用逗号运算符:
#define FOO(X) (f(X),g(X))
or even more exotically:
甚至更奇特:
#define FOO(X) g((f(X),(X)))
While this will work well with separate instructions, it will not work with cases where variables are constructed and used as part of the #define
:
虽然这适用于单独的指令,但它不适用于构造变量并将其用作以下部分的情况#define
:
#define FOO(X) (int s=5,f((X)+s),g((X)+s))
With this one would be forced to use the do/while construct.
有了这个,将被迫使用 do/while 结构。
回答by Isaac Schwabacher
Jens Gustedt's P99 preprocessor library(yes, the fact that such a thing exists blew my mind too!) improves on the if(1) { ... } else
construct in a small but significant way by defining the following:
Jens Gustedt 的P99 预处理器库(是的,这样的事情存在的事实也让我大吃一惊!)if(1) { ... } else
通过定义以下内容,以小而重要的方式改进了该结构:
#define P99_NOP ((void)0)
#define P99_PREFER(...) if (1) { __VA_ARGS__ } else
#define P99_BLOCK(...) P99_PREFER(__VA_ARGS__) P99_NOP
The rationale for this is that, unlike the do { ... } while(0)
construct, break
and continue
still work inside the given block, but the ((void)0)
creates a syntax error if the semicolon is omitted after the macro call, which would otherwise skip the next block. (There isn't actually a "dangling else" problem here, since the else
binds to the nearest if
, which is the one in the macro.)
这样做的基本原理是,与do { ... } while(0)
构造不同,break
并且continue
仍然在给定块内工作,但是((void)0)
如果在宏调用之后省略分号,则会产生语法错误,否则会跳过下一个块。(这里实际上没有“悬空 else”问题,因为else
绑定到最近的if
,也就是宏中的那个。)
If you are interested in the sorts of things that can be done more-or-less safely with the C preprocessor, check out that library.
如果您对使用 C 预处理器或多或少可以安全完成的各种事情感兴趣,请查看该库。
回答by Mike Meyer
For some reasons I can't comment on the first answer...
由于某些原因,我无法对第一个答案发表评论......
Some of you showed macros with local variables, but nobody mentioned that you can't just use any name in a macro! It will bite the user some day! Why? Because the input arguments are substituted into your macro template. And in your macro examples you've use the probably most commonly used variabled name i.
你们中的一些人展示了带有局部变量的宏,但没有人提到你不能在宏中使用任何名称!总有一天它会咬用户!为什么?因为输入参数被替换到您的宏模板中。在您的宏示例中,您可能使用了最常用的变量名称i。
For example when the following macro
例如当下面的宏
#define FOO(X) do { int i; for (i = 0; i < (X); ++i) do_something(i); } while (0)
is used in the following function
在以下函数中使用
void some_func(void) {
int i;
for (i = 0; i < 10; ++i)
FOO(i);
}
the macro will not use the intended variable i, that is declared at the beginning of some_func, but the local variable, that is declared in the do ... while loop of the macro.
宏不会使用在 some_func 开头声明的预期变量 i,而是使用在宏的 do ... while 循环中声明的局部变量。
Thus, never use common variable names in a macro!
因此,永远不要在宏中使用公共变量名!
回答by C?ur
Explanation
解释
do {} while (0)
and if (1) {} else
are to make sure that the macro is expanded to only 1 instruction. Otherwise:
do {} while (0)
并if (1) {} else
确保将宏扩展为仅 1 条指令。除此以外:
if (something)
FOO(X);
would expand to:
将扩展为:
if (something)
f(X); g(X);
And g(X)
would be executed outside the if
control statement. This is avoided when using do {} while (0)
and if (1) {} else
.
并且g(X)
会在if
控制语句之外执行。使用do {} while (0)
and时可以避免这种情况if (1) {} else
。
Better alternative
更好的选择
With a GNU statement expression(not a part of standard C), you have a better way than do {} while (0)
and if (1) {} else
to solve this, by simply using ({})
:
使用 GNU语句表达式(不是标准 C 的一部分),您有比do {} while (0)
和更好的方法if (1) {} else
来解决这个问题,只需使用({})
:
#define FOO(X) ({f(X); g(X);})
And this syntax is compatible with return values (note that do {} while (0)
isn't), as in:
并且此语法与返回值兼容(注意do {} while (0)
不是),如下所示:
return FOO("X");
回答by John Nilsson
I don't think it was mentioned so consider this
我不认为它被提及所以考虑这个
while(i<100)
FOO(i++);
would be translated into
将被翻译成
while(i<100)
do { f(i++); g(i++); } while (0)
notice how i++
is evaluated twice by the macro. This can lead to some interesting errors.
注意i++
宏如何计算两次。这可能会导致一些有趣的错误。