美文网首页设计设计语言IT@程序员猿媛
do{...}while(0U)的作用于意义

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

作者: konishi5202 | 来源:发表于2019-03-19 10:13 被阅读8次

    很多初学者,以及有一定工作经验的朋友都不知道这个“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 <stdio.h>
    #include <stdlib.h>
    
    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 <stdio.h>
    #include <stdlib.h>
    
    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)
    

    相关文章

      网友评论

        本文标题:do{...}while(0U)的作用于意义

        本文链接:https://www.haomeiwen.com/subject/ofnwmqtx.html