• 你想知道的do{...}while(0)的作用,都在这里了


    0、引言        

            我们在嵌入式开发的过程中,经常可以碰到在一些宏定义或者是代码段中使用了do {...} while(0)的语句,从语义上理解,do {...} while(0)内的逻辑就只执行一次,并没有循环执行,粗略看来,似乎画蛇添足了,那么为什么还需要在只执行一次的逻辑外面加上一层do {...} while(0)语句呢?实际上,在这些逻辑中使用do {...} while(0)的作用远大于美化你的代码,下面就来看看实际的使用场景。


    1、用于定义一个作用域,避免替换的时候出错

            我们都知道,在程序中如果一些常量参数或者代码语句反复出现,就可以使用宏定义来替代。预处理阶段,对程序中所有出现的“宏名”,预处理器都会用宏定义中的字符串替代,这称为“宏替换”或“宏展开”。

            这样做可提高程序的通用性和易读性,减少不一致性,一个较好的宏名可以更好的让读者理解常量参数的含义;同时程序易于修改,我们仅需要改变一个宏定义,就可以改变整个程序中出现的所有该常量或者语句。

            但是有时可能程序代码段中,出现多条语句重复连续的使用,这样我们就可以尝试使用一个复杂的宏来替换。你有可能会这样定义:

    1 #define REPLACE_FUN() funA(); funB()

       本意是在程序中当出现funA()和funB()多条语句连续使用时,使用REPLACE_FUN()来替换。

    1 if(判断条件)
    2     REPLACE_FUN();

            但是实际上在预处理的时候,宏展开替换后变成了:

    1 if(判断条件)
    2    funA();
    3 funB();   //此处funB()一定会执行,造成逻辑错误

            可以看出,funB()不会按照判断条件才去执行。而是变成了一条独立的语句,而如果在宏中使用括号:

    1 #define REPLACE_FUN() {funA(); funB();}

            我们一般的代码习惯都会在语句的末尾加上分号,因此也会出错:

    复制代码
    1 if(判断条件)
    2     REPLACE_FUN();
    3 //宏展开后为:
    4 if(判断条件)
    5 {
    6     funA();
    7     funB();
    8 };    //此处替换后多一个分号;导致编译报错
    复制代码

            因此,针对这种多条重复语句的连续使用,如果想用宏替换实现这个作用域的功能,就可以考虑使用do {...} while(0)语句:

    复制代码
     1 define REPLACE_FUN() \
     2         do{ \
     3             funA();\
     4             funB();\
     5           }while(0)\
     6 //宏展开前为:         
     7 if(判断条件)
     8     REPLACE_FUN();
     9 //宏展开后为:
    10 if(判断条件)
    11      do{
    12           funA();
    13           funB();
    14      }while(0);    //根据判断条件,正确执行了一次逻辑
    复制代码


    2、避免goto语句的使用

            goto语句也称为无条件转移语句,使用后可以从多重循环或者多个判断中直接跳出。对于如下例子:

    复制代码
     1 void fun(int a)
     2 {
     3    if(1 == a)
     4    {
     5        ...//todo
     6        goto exit;
     7    }  
     8    if(2 == a)
     9    {
    10      ...//todo
    11      goto exit;
    12    }
    13 exit:
    14    ...//todo
    15    printf("a is error"\n);
    16 }
    复制代码

            但是为了程序结构的清晰,还是要尽量限制goto语句的使用,我们可以使用do {...} while(0)结构配合break跳出单层的循环的方法来替代这种goto的用法。

    复制代码
     1 int fun(int a)
     2 {
     3    do{
     4        if(1 == a)
     5        {
     6          ...//todo
     7          break;
     8        }  
     9        if(2 == a)
    10        {
    11          ...//todo
    12          break;
    13        }
    14    }while(0);
    15    ...//todo
    16    printf("a is error"\n);
    17 }
    复制代码

    3、定义一个单独的函数块来实现复杂的操作

            当某个函数程序功能较为复杂,在该函数的代码段中如果不再单独定义一个函数实现部分逻辑,可以使用do {...} while(0)作为一个代码块,将想要实现的逻辑放在do {...} while(0)中,同时在该在do {...} while(0)代码块中定义的变量,可以不用考虑和函数之前或者之后的变量名重复冲突的问题。但是为了代码的易读性,还是尽量声明不同的变量名。

    复制代码
     1 int a;
     2 char b;
     3 int func()
     4 {
     5     int a = 3;
     6     char b = 5;
     7     do{
     8         int a;
     9         char b;
    10         ......//todo
    11     }while(0);    
    12 }
    复制代码

    4、避免空宏的警告

            有的时候,程序为了不同的平台移植或者不同架构的限制,很多时候会先定义空宏,后续再根据实际的需要看是否定义具体内容。但是在编译的时候,这些空宏可能会给出warning,为了避免这样的warning,我们可以使用do{...}while(0)来定义空宏,这种情况不太常见,因为有很多编译器已经支持空宏。

    1 //空宏
    2 #define EMPTY_FUN
    3 //增加do{...}while(0)来定义空宏
    4 #define EMPTY_FUN do{}while(0) //避免了可能的编译warning

    更多技术内容和书籍资料获取,入群技术交流敬请关注“明解嵌入式”

  • 相关阅读:
    Linux6.1中为什么用Radix树替换位图(bitmap)来管理进程pid
    【Vue】数据表格增删改查与表单验证
    【JavaEE】MyBaits(注解方式实现映射)
    LeetCode 周赛 342(2023/04/23)容斥原理、计数排序、滑动窗口、子数组 GCB
    计算机网络:运输层
    java8 function接口浅析(个人见解,如有错误,还请指正)
    lxm not found:Mac Python 安装 lxml包
    SaaSBase:Flowportal是什么?
    【设计模式】装饰器模式( Decorator Pattern)
    c++game大全
  • 原文地址:https://www.cnblogs.com/Sharemaker/p/17142670.html