嵌入式宏定义中do...while的意义

Posted PORKWOTONLEE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了嵌入式宏定义中do...while的意义相关的知识,希望对你有一定的参考价值。

目录

背景

最近这段时间都忙着使用QT环境写C++的项目,最近终于有时间复习嵌入式的项目。在浏览某些功能库代码时发现一个很有意思的技巧:do…while(0)形式宏定义,形式如下:

// do...while(0)形式宏定义
#define FUN_DRINK dofind_bottle();\\
				     fill_water();\\
				     drink_water();while(0)				   

刚开始看到这样的宏定义的时候的时候有点懵,do…while(0)执行一次不就退出循环了嘛,有啥用呢。直接用一般的形式就好了:

// 相对普遍的一般宏定义
#define FUN_DRINK find_bottle();\\
				  fill_water();\\
				  drink_water()

于是花了点时间看了一些资料,算是明白了。


基于个人理解的总结:

do…while(0)形式宏定义使得宏定义拥有一些函数的特点


分析

使用do…while(0)形式的后,宏定义便会有以下的特点:

1. 封装:使得宏定义可以包含复杂的内容而不容易出错,提高代码健壮性

当宏定义中包含相对复杂的内容时,常用的宏定义形式为:

// 一般形式
#define FUN_DRINK find_bottle();\\
				  fill_water();\\
				  drink_water()
// 外加大括号形式
#define FUN_DRINK find_bottle();\\
				  fill_water();\\
				  drink_water();

如果需要在条件判断语句中使用以上宏定义,代码的未展开/展开的形式分别为

// 未展开
if (I_AM_THRISTY)
	FUN_DRINK;
else
	FUN_RUN;
		
// 一般形式展开
if (I_AM_THRISTY)
	find_bottle();
	fill_water();
	drink_water();
else
	...; // 省略
// 外加大括号形式展开
if (I_AM_THRISTY)

	find_bottle();
	fill_water();
	drink_water();
;
else
	...; // 省略

很显然,以上语句都是会报错的,不是我们预期的结果。而使用do…while(0)形式的宏定义后,代码展开变成了

// 展开
if (I_AM_THRISTY)
	do
		find_bottle();
		fill_water();
		drink_water();
	while(0);
else
	...; // 省略

这样的代码可以正常运行,do…while(0)被看作完整语句块,整体执行,不容易出错,这就类似于函数的封装

宏定义可以包含多个语句块或者是多个其他宏定义嵌套,但如果内容过于复杂的话最好还是通过函数实现

2. 生命周期:宏定义内/外部定义的同名变量不会冲突

如果宏定义中包含了一些变量的定义,而恰好宏定义外部也有同名变量的定义的话,是会出现重定义错误的

// 一般形式
#define ADD_OFFSET(a) int offset=50;a+=offset;

// 未展开
int offset = 0;
int a = 300;
ADD_OFFSET(a);
printf("offset = %d", offset);
printf("a = %d", a);

// 展开
int offset = 0;
int a = 300;
int offset=50; // 重定义错误
a+=offset;
printf("offset = %d", offset);
printf("a = %d", a);

使用do…while(0)形式的宏定义后展开变成了

int offset = 0;
int a = 300;

do
	int offset=50;
	a+=offset;
while(0)

printf("offset = %d", offset);
printf("a = %d", a);

很显然,内部变量的生命周期到循环结束就销毁了,作用域也只在do…while(0)循环体内,不会影响到外部同名变量的定义,这就类似于函数内部变量的生命周期

3. 返回:提供类似函数return的出口,宏定义逻辑更为灵活清晰

这一点,是利用了break语句可以退出循环体的特性。举例如下:

#define MULTI_TASK(choice) doif(choice==0)fun0();break;;
							  if(choice==1)fun1();break;;
							  ...while(0)

// 未展开
MULTI_TASK(0);
MULTI_TASK(1);

// 展开
do
	if(0==0)
	
		fun0();
		break;
	;
   if(0==1)
   
       fun1();
   	   break;
   ;
...while(0);
do
	if(1==0)
	
		fun0();
		break;
	;
   if(1==1)
   
       fun1();
   	   break;
   ;
...while(0);						

通过break退出循环,相当于给代码提供了一个灵活的出口,使得宏定义中的内容可以被有选择地运行,而不用一股脑地全部运行直到while(0)之后退出,这就类似于函数的return

总结

虽然很多书籍/教材都不提倡用宏定义,原因是宏定义如果用不好可能会引发更多的麻烦,并且Debug的时候也不太好找,导致很多Coder对宏定义都避而远之

但是宏定义实际上如果运用的好的话,是一个非常好用的工具,说到底还是个人能力问题(菜是原罪),有能力还是要多学习,毕竟技多不压身嘛

嵌入式编程中使用 do{...} while 的解释

最近在看esp32的idf,有一些宏定义使用了do while(0)这种看起来好像没啥用的代码。然后我查了一下资料,发现在linux内核代码中经常用到这个东西!

现在就将这个东西整理一下。

        

为什么在内核中碰到很多  

#define ...  do{...} while(0) 

 

有以下几点原因:

1、空语句在编译的时候会出现警告,所以有必要用#define FOO do{ } while(0)

2、给定一个基本块,可以在里面定义局部变量

3、为了能够在条件语句中使用复杂的宏定义。例如下面这段代码:

#define FOO(x) 

     printf("arg is %s\n", x); 

     do_something_useful(x);

如果这样用:

    if (blah == 2)    F00(blah);

将会被展开为:

  if (blah == 2)

              printf("arg is %s\n", blah);

              do_something_useful(blah);

 这样,if条件之后包含了printf()语句,而do_something_useful()调用不能按照预期那样工作。

 

而用do {...} while(0)定义后,就会展开成以下语句:   

if (blah == 2)

do{

              printf("arg is %s\n", blah);

              do_something_useful(blah);

}while(0);

这是我们所期望的。

 

如果你希望定义一个包含多行语句和一些局部变量的时候. 一般的定义方式只能这样:

#define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; }

然而在某些情况下,这样并不能正常工作. 下面是包含两个分支的if语句:

 if (x > y)

  exch(x,y);          // Branch 1

  else

  do_something();     // Branch 2

但这样却只能展开成单分支的if语句,如下:

if (x > y) {                // 单分支if

  int tmp;

  tmp = x;

  x = y;

  y = tmp;

  }

  ;                           // 空语句

  else                        // 语法错误! "parse error before else"

  do_something();

问题是由于在语句块后直接加入分号(;)引起的。

 

解决办法是将语句块放入 do 和 while (0)中间.这样就得到了一条单语句, 而不是被编译器判断为语句块.现在的if语句如下:

if (x > y)

  do {

      int tmp;

      tmp = x;

      x = y;

      y = tmp;

  } while(0);

 else

  do_something();

 

假设有这样一个宏定义  
 

#define  macro(condition)  if(condition)  do_something();  

 

现在在程序中这样使用这个宏:  

if(temp)  
             macro(i);  
else  
             do_anotherthing();  

一切看起来很正常,但是仔细想想。这个宏会展开成:  

if(temp)  
             if(condition)  do_something();  
else    
             do_anotherthing();  

 

这时的else不是与第一个if语句匹配,而是错误的与第二个if语句进行了匹配,编译通过了,但是运行的结果一定是错误的。  
 
为了避免这个错误,我们使用do{….}while(0)  把它包裹起来,成为一个独立的语法单元,从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低。  

 

参考资料

http://blog.csdn.net/chenhu_doc/article/details/856468

http://www.233.com/linux/fudao/20101102/140715235.html

以上是关于嵌入式宏定义中do...while的意义的主要内容,如果未能解决你的问题,请参考以下文章

do{} while 只执行一次无意义?你可能真的没理解

嵌入式编程中使用 do{...} while 的解释

C语言的奇技淫巧(1-50)

项目中大量使用do{...} while(0U)的作用和意义

C 语言宏定义中使用do...while

do{...}while的意义和用法