嵌入式宏定义中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的意义的主要内容,如果未能解决你的问题,请参考以下文章