在freeglut库中,我在文件中freeglut_callbacks.c找到了以下代码:
/*
* All of the callbacks setting methods can be generalized to this:
*/
#define SET_CALLBACK(a) \
do \
{ \
if( fgStructure.CurrentWindow == NULL ) \
return; \
SET_WCB( ( *( fgStructure.CurrentWindow ) ), a, callback ); \
} while( 0 )
我不明白为什么这里需要一个循环,因为无论如何它只会执行一次,不是吗?
该构造广泛用于“功能性”宏,旨在将宏内的多个语句和声明组合成一个复合语句。这样的联合是必要的,这样宏就可以在条件运算符、循环等的分支中用作常规函数调用。无需不断地将调用包装在外部对中
{ ... }乍一看,只需将宏定义本身
{ ... }与配对即可实现相同的目的do { ... } while (0)。但是,选项 c{ ... }会有一个令人不快的缺点 - 需要不断记住他的身体是{ ... },然后{ ... }在 C 中并不总是可以放;.例如,如果 body
SET_CALLBACK简单地包含在 中{ ... },那么这个看起来很自然的代码将不会编译,因为由于它后面的SET_CALLBACK(foo)分号,分支else被从它自己的分支中撕掉了if这是它进入舞台的地方
do { ... } while (0)。循环do/while的独特之处在于它(几乎)是 C 语言中唯一形成块的语法结构,并且它总是无条件地以;. 多亏了这个闭包;,我们可以do { ... } while (0)以更自然的方式使用复合宏,将它们组合成一条语句。在上面的示例中, cif;之后的 cSET_CALLBACK(foo)将被构造“吸收”do { ... } while (0)并且不会“破坏” 的完整性if。这是为了能够通过我们调用普通函数的相同自然语法来“调用”函数宏,复合宏的主体不是包含在 中
{ ... },而是包含在do { ... } while (0)PS为什么该语言的作者曾经决定坚持在
;语法中包含一个[理论上完全没有必要的] 结束语,这do/while一点并不完全清楚,但已经完成了。这是一个非常有用的结构。由于宏是“按原样”替换的,因此可能会发生各种奇怪的事情。假设我们有这样一个宏
(人为的例子)
如果现在这样打电话
然后它会展开成
当然,它根本不会像用户期望的那样工作——变量声明显然不存在并且没有足够的括号。
原则上,只用大括号就可以解决问题,但是带有的构造提供
do-while(0)了一个重要的优势——使用 break 退出宏的能力,这使宏更接近于常规函数。