< 返回博客

C语言中的do-while(0)


在看libev源码的时候,发现有好多的宏定义为类似#define ev_io_set(ev,fd_,events_) do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while (0)的样子,这种宏有什么用?

do{…}while(0)在C中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。

什么意思呢?其实就和在宏中给变量赋值的时候,要把变量用括号包裹起来是一个道理。因为C中的宏只是简单的文本替换,所以在宏展开的时候很容易出现意想不到的问题,简单的解决方式就是将可能出错的地方用括号包裹起来,比如:

#define mul(x, y) x * y
printf("%d\n", mul(1, 2));
printf("%d\n", mul(1 + 2, 1 + 2));

在第一个mul中,mul(1, 2)被展开成printf("%d\n", 1 * 2);,在这里是没有问题的;但是在第二个mul中,被展开成printf("%d\n", 1 + 2 * 1 + 2);,这里就有问题了,结果明显不对,所以我们需要用括号包裹,确保运算结合正确:

#define mul(x, y) ((x) * (y))
printf("%d\n", mul(1, 2));
printf("%d\n", mul(1 + 2, 1 + 2));

这样,第二个例子就会被展开为printf("%d\n", ((1 + 2) * (1 + 2)));,就不会有问题了。注意((x) * (y))整体的括号,为了确保不受外部影响,这也是必不可少的。

回到上面的问题,do while(0)其实原理也差不多,考虑下面的代码:

#define foo() bar(); baz()
if (1)
    foo();
else
    func();

很明显,上面的代码展开后会出现语法错误,因为foo()被展开成了两条语句,导致if语句提前结束。可能会想到的解决方式是:

#define foo() { bar(); baz(); }

但是这样的话,上面的if语句被展开为:

if (1) {
    bar();
    baz();
};
else
    func();

又出现语法错误了。所以,就有了上面的解决方案:利用do while循环有大括号的同时末尾也有分号的特性,保护中间的内容:

#define foo() do { bar(); baz(); } while (0)

这样,if被展开为:

if (1)
    do {
        bar();
        baz();
    } while (0);
else
    func();

就不会出现语法错误了,也就实现了不管怎么使用宏,宏后面的分号也是相同的效果的效果😆。