首先,在C语言中,与while有关的两个循环结构定义如下:
1- while(条件) {...}
2- do{...} while(条件);
注意第2个循环最后的那个分号,为什么会有这个分号?因为第1个循环被作为一个整体,可以被编译器识别,而第2个循环,如果没有最后的分号,编译器就不知道while是修饰前面的语句,还是像第1个一样修饰后面的语句,这里的分号起到了断句,消除歧义的作用。
这个分号在Linux内核代码中成为了一种所谓的编程技巧。
在Linux内核中,可以看到这样的宏定义:
#define MACRO_NAME do{...}while(0)
这个宏执行的动作就是让{...}执行一次,可为什么非要这么定义? 看以下的情景:
如果宏定义如此:
#define MACRO_NAME a=10;b=20;
if(...)
;
else
...;
经过宏展开之后,变成:
if(...)
;
else
...;
编译就会出错,因为找不到else匹配的if。这是因为我们宏定义的时候,没有把定义的语句做成一个整体,导致宏展开的时候影响了上下文的语法结构。
那么,你一定会想到为宏定义加上一对大括号:
#define MACRO_NAME {a=10;b=20;}
展开之后,为:
if(...)
;
else
...;
这仍然会出现else不匹配的问题,因为那个红色的分号仍然在大括号外面。而这个红色分号的存在是因为我们在使用宏的时候,仍然习惯于采用C语言的语法。于是,我们想到这么使用宏:
if(...)
MACRO_NAME
else
...;
可这样,看起来似乎又不那么自然,起码表面上看不符合C语言的语法形式。
归根结底,最好的方式是,在出现宏调用的地方,把整个宏调用包括分号用一对大括号括起来。如下:
if(...)
{MACRO_NAME;}
else
...;
这样,无论你在定义宏的时候是否加上分号,只要在调用宏的时候按照C语言的语法加上分号,就绝对不会出错,也不必在宏定义中
使用奇怪的do{...}while(0)循环了。
小结:
其实这种做法和采用do{...}while(0)定义宏的做法目的本质上一样,都是为了让编译器把宏调用处的分号与宏调用看作一个整体,只不过我是显式的
利用一对大括号直接括起来,而它是隐式的利用了C语言中的do{...}while(0);语法结构。采用do{...}while(0)的方式看起来有些古怪,但更简练。