三言两语聊Kernel:do{…}while(0)

在内核代码里我们会看到很多do{…}while(0)来定义的宏,比如(摘自include/asm-generic/barrier.h):

linux kernel里这么定义自然有其特殊的目的。

在C语言里,使用do/while(0)模式来定义的宏在任何情况下都有同样的行为,而且在C语言里面,只有do/while(0)模式来定义的宏才会在任何情况下(比如,在没有“ }”的if语句中)都有同样的行为.

举一些例子。

1
define foo(x)  a(x); b(x)

对于 foo(test);
会被展开为: a(test); b(test);

如果是这样的话,这看起来很正确,没有任何错误,是你原本想要得到的结果。

但如果这样用:

1
2
  if (cond)
  foo(test);

它就会被展开为:

1
2
   if (cond)
      a(test); b(test);

它的行为就变成了:

1
2
3
   if (cond)
      a(test);
      b(test);

这并不是你原本想要的。

然后,我们重新定义该宏,使用do/while(0)来封装:

1
define foo(x) do{a(x); b(x)}while(0)

这样定义的宏和前面定义的宏具有同样的作用,只不过,do确保了它的整个逻辑都在大括号里面执行,while(0)确保它只执行一次。所以它跟没有do/while(0) 的宏具有同样的效果。那么,他们有什么不同吗?让我们再来看前面那个例子:

1
2
if (cond)
    foo(test);

现在它就变成了:

1
2
if(cond)
    do{a(test); b(test)}while(0);

实际上就是:

1
2
3
4
if(cond){
    a(test);
    b(test);
}

你可能会疑问,为什么不直接使用“{}”来定义该宏?让我们来看下面这种情况:

1
#define foo(x) {a(x); b(x);}

那么:

1
2
3
4
if(cond)
    foo(test);
else
    bin(test);

就变成了:

1
2
3
4
5
6
if(cond){
    a(test);
    b(test);
};
else
    bin(test);


显然这是一个语法错误,因为else前面没有if。

使用这种方式来定义宏,还有个好处是,在”{…}“里面可以定义自己的局部变量,而不被外面的变量所干扰。

在linux内核代码里,很多宏都是使用do/while(0)来封装的,目的就是为了让它在任何情况下都具有同样的行为。在我们自己的代码里,也要养成这种好习惯。

Comments