在阅读Aspects源码时,发现了这样一个宏

#define AspectError(errorCode, errorDescription) do { \
AspectLogError(@"Aspects: %@", errorDescription); \
if (error) { *error = [NSError errorWithDomain:AspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0)

引起了我的注意,觉得比较诡异,因为如果只执行一次,do...while没卵用,但是人家这么定义肯定有其中的道理,学习摸索了一番记下来,权当笔记。

Robert Love(Google的软件工程师,热衷于Linux内核维护)是这么说的

The do/while(0) pattern seen in many if not most macros in the Linux kernel and elsewhere has a specific purpose: It is the only construct in C that lets you define macros that always work the same way, so that a semicolon after your macro always has the same effect, regardless of how the macro is used (with particularly emphasis on the issue of nesting the macro in an if without curly-brackets).
大体意思是说,用do/while(0)模式来定义的宏可以在任何情况下都有同样的行为,即便是少了大括号。

1. 防止意外出错

比如,

#define foo(x)  bar(x); baz(x)

当我们foo(x);这么调用是完全没有问题的,但是在下面的情况就不一样了

if(NO) foo(x);

上面的代码实际被展开为

if(NO) bar(x);
baz(x);

很明显不管if的条件是否成立,baz(x)都会得到执行,这样很容易由于疏忽而造成错误。

2. 为了信仰

很容易想到,那么不使用do/while(0)也可以完全做到,比如我们可以这样

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

这样定义的话请考虑如下

if(YES) foo(x);
else foo2(x);

上面代码会展开为

if(YES) {
   bar(x);
   baz(x);
};else foo2(x);

很明显已经出错了,我们当然可以说在这里我们不加引号就好了,但是我们无法控制别人使用自己代码时使用宏的场景,像这种就更容易习惯性的加上分号而导致错误,突然某句不加分号觉得怪怪的。此时如果使用do/while(0)模式来定义的话就不会出现这种问题。

用do/while(0)模式来定义的宏可以在任何情况下都具有同样的行为,这就是do/while(0)中的奥秘。

Reference

1.What is the purpose of using do {...} while (0) in macros?
2.do { … } while (0) — what is it good for?