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

为什么在宏中使用明显无意义的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)


慕妹3242003
浏览 831回答 4
4回答

胡说叔叔

该do ... while和if ... 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预处理器时,这是他们担心的事情。

四季花海

宏是复制/粘贴的文本,预处理器将放入正版代码中;&nbsp;宏的作者希望替换产生有效的代码。有三个好的“提示”可以成功:帮助宏的行为像真正的代码正常代码通常以分号结束。如果用户查看不需要的代码......doSomething(1)&nbsp;;DO_SOMETHING_ELSE(2)&nbsp;&nbsp;//&nbsp;<==&nbsp;Hey?&nbsp;What's&nbsp;this?doSomethingElseAgain(3)&nbsp;;这意味着如果没有分号,用户希望编译器产生错误。但真正正确的理由是,在某些时候,宏的作者可能需要用真正的函数(也许是内联函数)替换宏。所以宏应该真的像一个。所以我们应该有一个需要分号的宏。生成有效的代码如jfm3的答案所示,有时宏包含多条指令。如果宏在if语句中使用,这将是有问题的:if(bIsOk) &nbsp;&nbsp;&nbsp;MY_MACRO(42)&nbsp;;此宏可以扩展为:#define&nbsp;MY_MACRO(x)&nbsp;f(x)&nbsp;;&nbsp;g(x)if(bIsOk) &nbsp;&nbsp;&nbsp;f(42)&nbsp;;&nbsp;g(42)&nbsp;;&nbsp;//&nbsp;was&nbsp;MY_MACRO(42)&nbsp;;g无论值如何,都将执行该功能bIsOk。这意味着我们必须向宏添加一个范围:#define&nbsp;MY_MACRO(x)&nbsp;{&nbsp;f(x)&nbsp;;&nbsp;g(x)&nbsp;;&nbsp;}if(bIsOk) &nbsp;&nbsp;&nbsp;{&nbsp;f(42)&nbsp;;&nbsp;g(42)&nbsp;;&nbsp;}&nbsp;;&nbsp;//&nbsp;was&nbsp;MY_MACRO(42)&nbsp;;生成有效的代码2如果宏是这样的:#define&nbsp;MY_MACRO(x)&nbsp;int&nbsp;i&nbsp;=&nbsp;x&nbsp;+&nbsp;1&nbsp;;&nbsp;f(i)&nbsp;;我们可能在以下代码中遇到另一个问题:void&nbsp;doSomething(){ &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;25&nbsp;; &nbsp;&nbsp;&nbsp;&nbsp;MY_MACRO(32)&nbsp;;}因为它会扩展为:void&nbsp;doSomething(){ &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;25&nbsp;; &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;32&nbsp;+&nbsp;1&nbsp;;&nbsp;f(i)&nbsp;;&nbsp;;&nbsp;//&nbsp;was&nbsp;MY_MACRO(32)&nbsp;;}当然,这段代码不会编译。所以,再一次,解决方案是使用范围:#define&nbsp;MY_MACRO(x)&nbsp;{&nbsp;int&nbsp;i&nbsp;=&nbsp;x&nbsp;+&nbsp;1&nbsp;;&nbsp;f(i)&nbsp;;&nbsp;}void&nbsp;doSomething(){ &nbsp;&nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;25&nbsp;; &nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;int&nbsp;i&nbsp;=&nbsp;32&nbsp;+&nbsp;1&nbsp;;&nbsp;f(i)&nbsp;;&nbsp;}&nbsp;;&nbsp;//&nbsp;was&nbsp;MY_MACRO(32)&nbsp;;}代码再次正常运行。结合分号+范围效应?有一个产生这种效果的C / C ++习语:do / while循环:do{ &nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;code}while(false)&nbsp;;do / while可以创建一个范围,从而封装宏的代码,最后需要一个分号,从而扩展为需要一个代码的代码。奖金?C ++编译器将优化do / while循环,因为其后置条件为false的事实在编译时是已知的。这意味着像一个宏:#define&nbsp;MY_MACRO(x)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\do&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;\{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;int&nbsp;i&nbsp;=&nbsp;x&nbsp;+&nbsp;1&nbsp;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;\ &nbsp;&nbsp;&nbsp;&nbsp;f(i)&nbsp;;&nbsp;g(i)&nbsp;;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;\}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;\while(false)void&nbsp;doSomething(bool&nbsp;bIsOk){ &nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;25&nbsp;; &nbsp;&nbsp;&nbsp;if(bIsOk) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;MY_MACRO(42)&nbsp;; &nbsp;&nbsp;&nbsp;//&nbsp;Etc.}将正确扩展为void&nbsp;doSomething(bool&nbsp;bIsOk){ &nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;25&nbsp;; &nbsp;&nbsp;&nbsp;if(bIsOk) &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;do &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;const&nbsp;int&nbsp;i&nbsp;=&nbsp;42&nbsp;+&nbsp;1&nbsp;;&nbsp;//&nbsp;was&nbsp;MY_MACRO(42)&nbsp;; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f(i)&nbsp;;&nbsp;g(i)&nbsp;; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;while(false)&nbsp;; &nbsp;&nbsp;&nbsp;//&nbsp;Etc.}然后编译和优化void&nbsp;doSomething(bool&nbsp;bIsOk){ &nbsp;&nbsp;&nbsp;int&nbsp;i&nbsp;=&nbsp;25&nbsp;; &nbsp;&nbsp;&nbsp;if(bIsOk) &nbsp;&nbsp;&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f(43)&nbsp;;&nbsp;g(43)&nbsp;; &nbsp;&nbsp;&nbsp;} &nbsp;&nbsp;&nbsp;//&nbsp;Etc.}

月关宝盒

您可能还想补充一点,宏语法也可以使用简单的“if”语句防止可能更危险(因为没有错误)的意外行为:#define&nbsp;FOO(x)&nbsp;&nbsp;f(x);&nbsp;g(x)if&nbsp;(test)&nbsp;FOO(&nbsp;baz);扩展为:if&nbsp;(test)&nbsp;f(baz);&nbsp;g(baz);这在语法上是正确的,所以没有编译器错误,但可能有意外的结果,g()将始终被调用。

长风秋雁

上述答案解释了这些结构的含义,但两者之间存在显着差异,未提及。其实,还有一个原因,更喜欢do ... while到if ... else结构。if ... else构造的问题是它不会强迫你输出分号。喜欢这段代码:FOO(1)printf("abc");虽然我们遗漏了分号(错误地),但代码将扩展为if&nbsp;(1)&nbsp;{&nbsp;f(X);&nbsp;g(X);&nbsp;}&nbsp;elseprintf("abc");并将静默编译(虽然一些编译器可能会发出无法访问代码的警告)。但该printf声明永远不会被执行。do ... while构造没有这样的问题,因为之后唯一有效的标记while(0)是分号。
打开App,查看更多内容
随时随地看视频慕课网APP