do{...}while(0U)的作用与意义

很多初学者,以及有一定工作经验的朋友都不知道这个“do{...}while(0U)”的作用和意义,甚至觉得这样写的代码复杂且没有意义。

相信看过STM32的HAL库源码和Linux内核源码的朋友,都知道源码里面有许多do{...}while(0U)的宏定义语句。除了用于宏定义之外,do{...}while(0U)还有很多作用。

2.1 确保宏定义正确展开

Google的Robert Love(先前从事Linux内核开发)解释到:

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

简单理解就是:使用do{...}while(0U)构造后的宏定义不受使用的大括号、分号等的影响,而总会按照你期望的意图运行。

例如:

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

当如下调用:

foo(wolf);

则会被展开为:

bar(wolf); baz(wolf);

看来是没有问题。但要是结合if语句调用:

if(!value)
    foo(wolf);

则会被展开为:

if(!value)
    bar(wolf);
baz(wolf);

这显然就违背我们期望的效果了。但如果我们使用do{...}while(0U)来重新定义宏:

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

则展开的效果为:

if(!value)
    do {
        bar(x);
        baz(x);
    }while(0U)

2.2 实现局部作用域

我们知道宏定义只是做一个标识符和字符串的替换,尽管宏定义的形式可以类似函数,但是它实际并不具备与函数类似的局部作用域。

我们当然可以通过使用大括号的形式(如:#define func(x) {...}),来实现局部作用域,但是这样会带来新的麻烦:

#define swap(a, b) {a = a + b; b = a - b; a = a -b}

int main(void)
{
    int a = 1, b = 2;
    if(1)
        swap(a, b);
    else
        a = b = 0;
    return 0;
}

上面代码咋一看没有问题,但是对其进行宏展开后如下:

int main(void)
{
    int a = 1, b = 2;
    if(1)
    {
        a = a + b;
        b = a - b;
        a = a - b;
    };
    else
        a = b = 0;
    return 0;
}

这下问题就明显了:在if后的代码后面多出了一个‘;’,这会引发编译错误。使用该宏定义时不在后面加‘;’可以解决这个问题,但这显然不符合我们的编码习惯,且要求宏编写者和使用者必须使用统一的标准。而在宏定义中使用do{...}while(0U)就可以解决这个问题:

#define swap(a, b) do{ \
                a = a + b; \
                b = a - b; \
                a = a - b; \
            }while(0U)

int main(void)
{
    int a = 1, b = 2;
    if(1)
        swap(a, b);
    else
        a = b = 0;
    return 0;
}

展开后的效果为:

int main(void)
{
    int a = 1, b = 2;
    if(1)
        do{
            a = a + b;
            b = a - b;
            a = a - b;
        }while(0U);
    else
        a = b = 0;
    return 0;
}

这样我们就可以放心地在宏定义后面使用分号而不会造成问题了。

2.3 避免使用goto语句

在C/C++语言程序中,我们可能会发生错误以后做一些特殊处理(比如资源释放),如果顺利执行则直接退出,我们常使用goto来实现:

#include 
#include 

int main(void)
{
    int ret = -1;
    
    ret = func1();
    if(0 != ret)
        goto failed;
    
    ret = func2();
    if(0 != ret)
        goto failed;
    
    ret = func3();
    if(0 != ret)
        goto failed;
    
    return 0;

failed:
    do_err();
}

但是,不建议在程序中大量使用goto。虽然使用goto语句可以解决代码冗余,也能提高程序的灵活性与简洁性,但这样也会使程序结构变得混乱不易维护(我们更看重的是可维护性)。我们可以使用do{...}while(0U)来代替goto实现相同的功能:

#include 
#include 

int main(void)
{
    int ret = -1;
    
    do {
        ret = func1();
        if(0 != ret)
            break;
        
        ret = func2();
        if(0 != ret)
            break;
        
        ret = func3();
        if(0 != ret)
            break;
        
        return 0;
    }while(0U);
    
    do_err();
}

2.4 避免空声明在编译时出现警告

在Linux内核源代码中,经常看到如下宏定义以避免在编译时出现警告:

#define FOO do { }while(0U)

想了解更多有关嵌入式Linux驱动、应用、常用开源库、框架、模式、重构等相关相关的主题内容,请长安下面图片,关注我的公众号(只有技术干货分享,不拉稀摆带!)。

do{...}while(0U)的作用与意义_第1张图片
image

你可能感兴趣的:(do{...}while(0U)的作用与意义)