对linux内核源码的有一定了解的同学应该会发现,内核源码中很多地方都会使用宏定义,如果足够细心,内核代码在定义宏时,使用了一些表面看起来有些冗余但是实质上却不得不为之的一些技巧,本文要讲的do {} while(0)就是这样一种情况。下面就分析其作用。

我们先来看两个宏定义:

//第一种宏定义
#define fun(x)  f(x); g(x)

//第二种宏定义
#define fun(x)
do {
    f(x); 
    g(x);
}while(0)

看到这,有同学就说了,这两个宏的作用不都是一样吗,因为do {}while(0)其实就是将{}中语句执行一遍,没错,可能大部分情况下,这两种宏展开后功能一样,都能很好的完成工作。比如说代码中调用fun(8),上面两种宏展开后最终都是执行f(8)后再去执行g(8),效果完全一样。

既然这样,为什么内核中很多情况下确使用第二种看起来有点画蛇添足感觉的do{}while(0)形式来定义宏?相信我,写linux内核那帮哥们的水平肯定比我们强,至少比我是强到不知十万八千里呢。,这么做肯定是有其深层的原因。

我们再来看看下面这样一个调用,看看如果用第一种形式的宏展开后的代码:

//第一种宏定义
#define fun(x)  f(x); g(x)

if (x)
    fun(8);
printf("hello\n");

//展开后
if (x)
    f(8);g(8);  //x为假,会执行g(8)
printf("hello\n");

程序原意是如果x为假,直接执行printf("hello\n");但是如果用第一种宏定义展开,会发现,当x为假,会继续执行g(8)再去执行printf("hello\n");显然这不是我们期望的。

也许,聪明的你想到了一种解决上面问题的方案:第一种宏定义中将两个函数包含在一对{}中就好了,如下:

//第一种宏定义,加{},需要注意的是g(x)后需加上;
#define fun(x)  {f(x); g(x);}

if (x)
    fun(8);
printf("hello\n");

//展开后
if (x)
    {f(8);g(8);};  //x为假,现在不会执行g(8)了
printf("hello\n");

没错,上面的问题是解决了,但是,我们再来看看下面一种调用情况:

//第一种宏定义,加{}
#define fun(x)  {f(x); g(x);}

if (x)
    fun(8);
else
    printf("hello\n");

//展开后
if (x)
    {f(8);g(8);};  //if后有两条语句,语法错误
else
    printf("hello\n");

由于在C/C++中,;也是一个语句,因此上面的调用展开后if后面有两条语句,而C/C++语法中,if后面如果有配对的else,if后面不能有两个语句的,因此这是一种语法错误。看来,这种方案也不能最终解决问题,好了,现在我们用do {} while(0)的形式来展开上述两个调用看看。

//do {} while(0)形式宏定义
#define fun(x)
do {
    f(x); 
    g(x);
}while(0)

//第一种调用形式
if (x)
    fun(8);
printf("hello\n");

//展开后
if (x)
    do {
        f(x); 
        g(x);
    }while(0);//正常工作
printf("hello\n");



//第二种调用形式
if (x)
    fun(8);
else
    printf("hello\n");

//展开后
if (x)
    do {
        f(x); 
        g(x);
    }while(0);//正常工作
else
    printf("hello\n");

不难看出,改用do {} while(0)的形式的宏定义能很好的解决前面的问题。保证了宏在使用时,不管宏所在的位置花括号、分号如何写,宏的表现总是一致的,这样我们就不需要担心宏如何被使用,一劳永逸。

推荐阅读:

【福利】自己搜集的网上精品课程视频分享(上) 【系统设计】LRU缓存 【协议森林】邮差与邮局 (网络协议概观) 【数据结构与算法】 通俗易懂讲解 位排序 【C++札记】C++11并发编程(一)开启线程之旅 【C++札记】C/C++指针使用常见的坑 【C++札记】静态库和动态库详解(上)

码农有道 coding

码农有道,为您提供通俗易懂的技术文章,让技术变的更简单!