文章参考:(1条消息) C语言 -- do{…}while(0)的意义和用法_诸葛一帆丶的博客-CSDN博客
在学习STM32H743的代码过程中,经常碰到一些有关do{...}while(0)的宏定义。
如,在学习stm32h7xx_hal_rcc.h文件时,就有相关的宏定义:
#define __HAL_RCC_GPIOA_CLK_ENABLE() do { \
__IO uint32_t tmpreg; \
SET_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\
/* Delay after an RCC peripheral clock enabling */ \
tmpreg = READ_BIT(RCC->AHB4ENR, RCC_AHB4ENR_GPIOAEN);\
UNUSED(tmpreg); \
} while(0)
我们都知道do{...}while{0}循环只执行一次,那么宏定义中使用do{...}while{0}循环意义何在呢?以下为我在学习过程中的一些感悟。仅供参考。
1.将定义的复杂的宏用作一个整体来引用。
举例,有时候在使用宏定义时,往往需要将多个C语句汇集到一起来定义一个宏。即使用宏定义来定义一个函数,用以实现某种功能,我们姑且称之为宏函数。如x,y比较大小,x大则x,y的值互换。反之x,y的值不互换。对此我们首先想到的宏定义使用方法为:
#define swap(x,y) temp=x; x=y; y=temp; //定义宏函数。
此时,使用时。
int x=..., y=..., temp;
if(x>y)
swap(x,y); //调用宏函数,调用函数时一般都需要加分号,而宏定义中语句 y=temp;可以利用此处的分号,但因y=temp为有一条C语句,因此按照习惯加了一个分号
else...
编译器进行处理后的代码为:
int x=...;y=...;
if(x>y)
temp=x;
x=y;
y=temp;
; //空语句
else...
这时,编译的结果就和想要的结果完全相反,宏定义中的 x=y; y=temp; 语句是一定执行的,而语句 temp=x; 则是条件执行。这与预期的结果完全不符合。
那么,使用{}在宏定义中,将C语句包裹起来能解决问题吗?
#define swap(x,y) {temp=x; x=y; y=temp;}
此时,使用时。
int x=..., y=..., temp;
if(x>y)
swap(x,y);
else...
编译器进行处理后的代码为:
int x=..., y=..., temp;
if(x>y)
{
temp=x;
x=y;
y=temp;
}
; //空语句
else...
此时,if条件如果成立的话会先执行swap(x,y)的功能,之后有一条空语句,这会导致else语句没有相应的if语句与之匹配,编译不通过,甚至会发生错误的匹配。
总结:究其原因,swap(x,y)函数是由多个语句组成,而宏定义只是将相应的文档进行的替换,而后续的编译规则导致了编译出错。所以思考能否将swap(x,y)中所有的C语句合成一个整体的C语句来进行调用。而do{...}while(0)语句能够合理的解决这一问题。
#define swap do{\
temp=x;\
x=y;\
y=temp;\
}while(0)
此时,使用时:
int x=..., y=..., temp;
if(x>y)
swap(x,y);
else...
编译器进行处理后的代码为:
int x=..., y=..., temp;
if(x>y)
do
{
temp=x;
x=y;
y=temp;
}while(0);
else...
这样的话就能很好地解决以上遇到的问题。
注 宏定义用法很灵活,如
#define swap(x,y) temp=x; x=y; y=temp;
int x=..., y=..., temp;
if(x>y)
{
swap(x,y)
}
else...
也能实现相关功能,但由于我们常常习惯将swap(x,y)作为一个函数来进行调用,因此一般会在调用之后加分号,这样就可能会出现以上的错误,因此,实际中宏定义的使用很灵活,但是尽量需要一个统一的标准,因此,尽量使用do{...}while(0)来定义宏。
2.代替goto语句对程序起作用。
在有些函数中,在函数return之前,我们常常需要进行处理工作,比如free掉一块函数开始malloc的内存,goto一直都是一个比较简便的方法:
int foo()
{
somestruct* ptr = malloc(...);
dosomething...;
if(error)
{
goto END;
}
dosomething...;
if(error)
{
goto END;
}
dosomething...;
END:
free(ptr);
return 0;
}
总结:这种程序的执行流程一般为从上到下的顺序流程,在执行过程中可能需要进行判断,需要在结束前进行其他的处理工作。
由于goto不符合软件工程的结构化,而且有可能使得代码难懂,所以很多人都不倡导使用,那这个时候就可以用do{}while(0)来进行统一的管理:
int foo()
{
somestruct* ptr = malloc(...);
do{
dosomething...;
if(error)
{
break;
}
dosomething...;
if(error)
{
break;
}
dosomething...;
}while(0);
free(ptr);
return 0;
}
改程序中用break语句代替goto语句实现相关功能,结束前的处理工作在while循环之后,能保证一定实现。
3.避免编译器对空的宏定义报错。
内核中由于不同架构的限制,很多时候会用到空宏,在编译的时候,空宏会给出warning,为了避免这样的warning,就可以使用do{}while(0)来定义空宏:
#define EMPTYMICRO do{}while(0)
这部分自己不太懂,参考文章给出的解释。
4.定义一个单独的函数块来实现复杂的操作。
当你的功能很复杂,变量很多你又不愿意增加一个函数的时候,使用do{}while(0);,将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。
这个自己并没有遇到过,因此也不太理解。
受作者知识水平限制,文章在所难免会有一些错误,如果有错误欢迎大家提出来,也希望文章能对大家有所帮助。