宏定义中使用do{}while(0)的好处

 

#define MACRO_NAME(para) do{macro content}while(0)
 
的格式,总结了以下几个原因:
 
1,空的宏定义避免warning:
#define foo() do{}while(0)
2,存在一个独立的block,可以用来进行变量定义,进行比较复杂的实现。
3,如果出现在判断语句过后的宏,这样可以保证作为一个整体来是实现:
 
#define foo(x) /
action1(); /
action2();
 
在以下情况下:
if(NULL == pPointer)
foo();
 
就会出现action1和action2不会同时被执行的情况,而这显然不是程序设计的目的。
 
4,以上的第3种情况用单独的{}也可以实现,但是为什么一定要一个do{}while(0)呢,看以下代码:
#define switch(x,y) {int tmp; tmp="x";x=y;y=tmp;}
if(x>y)
  switch(x,y);
else //error, parse error before else
  otheraction();
 
在把宏引入代码中,会多出一个分号,从而会报错。
 
//------------------------------------------------
使用do{….}while(0) 把它包裹起来,成为一个独立的语法单元,
从而不会与上下文发生混淆。同时因为绝大多数的编译器都能够识别do{…}while(0)这种无
用的循环并进行优化,所以使用这种方法也不会导致程序的性能降低
 
在C++中,有三种类型的循环语句:for, while, 和do...while,
但是在一般应用中作循环时, 我们可能用for和while要多一些,do...while相对不受重视。
但是,最近在读我们项目的代码时,却发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。
 
1. do...while(0)消除goto语句。
通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,
当然,退出前先释放资源,我们的代码可能是这样:
 
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true); // 执行并进行错误处理
bOk = func1();
if(!bOk)
{
delete p;
p = NULL;
return false;
} bOk = func2();
if(!bOk)
{
delete p;
p = NULL;
return false;
} bOk = func3();
if(!bOk)
{
delete p;
p = NULL;
return false;
} // .......... // 执行成功,释放资源并返回
delete p;
p = NULL;
return true; }
 
这里一个最大的问题就是代码的冗余,而且我每增加一个操作,就需要做相应的错误处理,非常不灵活。
于是我们想到了goto:
version 2
bool Execute()
{
// 分配资源
int *p = new int;
bool bOk(true); // 执行并进行错误处理
bOk = func1();
if(!bOk) goto errorhandle; bOk = func2();
if(!bOk) goto errorhandle; bOk = func3();
if(!bOk) goto errorhandle; // .......... // 执行成功,释放资源并返回
delete p;
p = NULL;
return true; errorhandle:
delete p;
p = NULL;
return false; }
 
代码冗余是消除了,但是我们引入了C++中身份比较微妙的goto语句,
虽然正确的使用goto可以大大提高程序的灵活性与简洁性,
但太灵活的东西往往是很危险的,它会让我们的程序捉摸不定,
那么怎么才能避免使用goto语句,又能消除代码冗余呢?
请看do...while(0)循环:
version3
bool Execute()
{
// 分配资源
int *p = new int; bool bOk(true);
do
{
// 执行并进行错误处理
bOk = func1();
if(!bOk) break; bOk = func2();
if(!bOk) break; bOk = func3();
if(!bOk) break; // .......... }while(0); // 释放资源
delete p;
p = NULL;
return bOk; }
 
2 宏定义中的do...while(0)
如果你是C++程序员,我有理由相信你用过,或者接触过,至少听说过MFC, 在MFC的afx.h文件里面,
你会发现很多宏定义都是用了do...while(0)或do...while(false), 比如说:
 
#define AFXASSUME(cond)       do { bool __afx_condVal=!!(cond); ASSERT(__afx_condVal);
__analysis_assume(__afx_condVal); } while(0)

粗看我们就会觉得很奇怪,既然循环里面只执行了一次,
我要这个看似多余的do...while(0)有什么意义呢? 当然有!
为了看起来更清晰,这里用一个简单点的宏来演示:
 
#define SAFE_DELETE(p) do{ delete p; p = NULL} while(0)
 
假设这里去掉do...while(0),
 
#define SAFE_DELETE(p) delete p; p = NULL;
那么以下代码:
 
if(NULL != p) SAFE_DELETE(p)
else ...do sth...

就有两个问题,

1) 因为if分支后有两个语句,else分支没有对应的if,编译失败
2) 假设没有else, SAFE_DELETE中的第二个语句无论if测试是否通过,会永远执行。
你可能发现,为了避免这两个问题,我不一定要用这个令人费解的do...while,  我直接用{}括起来就可以了

#define SAFE_DELETE(p) { delete p; p = NULL;}

的确,这样的话上面的问题是不存在了,但是我想对于C++程序员来讲,

在每个语句后面加分号是一种约定俗成的习惯,这样的话,以下代码:

if(NULL != p) SAFE_DELETE(p);
else ...do sth...

其else分支就无法通过编译了(原因同上),所以采用do...while(0)是做好的选择了。

也许你会说,我们代码的习惯是在每个判断后面加上{}, 就不会有这种问题了,也就不需要do...while了,如:

if(...) 
{
}
else
{
}

诚然,这是一个好的,应该提倡的编程习惯,但一般这样的宏都是作为library的一部分出现的,

而对于一个library的作者,他所要做的就是让其库具有通用性,强壮性,

因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等

 

宏定义中使用do{}while(0)的好处 (转载)的更多相关文章

  1. define宏定义中的#,##,@#及\符号

    define宏定义中的#,##,@#及\符号 在#define中,标准只定义了#和##两种操作.#用来把参数转换成字符串,##则用来连接两个前后两个参数,把它们变成一个字符串. 1.# (string ...

  2. 宏定义中的##操作符和... and _ _VA_ARGS_ _

    1.Preprocessor Glue: The ## Operator 预处理连接符:##操作符 Like the # operator, the ## operator can be used i ...

  3. C语言在宏定义中使用语句表达式和预处理器运算符

    语句表达式的亮点在于定义复杂功能的宏.使用语句表达式来定义宏,不仅可以实现复杂的功能,而且还能避免宏定义带来的歧义和漏洞.下面以一个简单的最小值的宏为例子一步步说明. 1.灰常简单的么,使用条件运算符 ...

  4. C语言宏定义中的#和##的作用【转】

    本文转载自:http://my.oschina.net/shelllife/blog/123202 在宏定义中#和##的作用是:前者将宏定义的变量转化为字符串:后者将其前后的两个宏定义中的两个变量无缝 ...

  5. #define宏定义中## #@ # \ 符号使用

    C/C++ 宏命令的神奇用法. 先看下面三条语句: #define Conn(x,y)     x##y#define ToChar(x)     #@x#define ToString(x)    ...

  6. do {...} while (0) 在宏定义中的作用

    如果你是一名C程序员,你肯定很熟悉宏,它们非常强大,如果正确使用可以让你的工作事半功倍.然而,如果你在定义宏时很随意没有认真检查,那么它们可能使你发狂,浪费N多时间.在很多的C程序中,你可能会看到许多 ...

  7. C do {...} while (0) 在宏定义中的作用

    如果你是一名C程序员,你肯定很熟悉宏,它们非常强大,如果正确使用可以让你的工作事半功倍.然而,如果你在定义宏时很随意没有认真检查,那么它们可能使你发狂,浪费N多时间.在很多的C程序中,你可能会看到许多 ...

  8. C语言可变参数在宏定义中的应用

    在C语言的标准库中,printf.scanf.sscanf.sprintf.sscanf这些标准库的输入输出函数,参数都是可变的.在调试程序时,我们可能希望定义一个参数可变的输出函数来记录日志,那么用 ...

  9. C在宏定义中使用的语言可变参数

    于C标准库的语言,printf.scanf.sscanf.sprintf.sscanf入输出函数,參数都是可变的.在调试程序时.我们可能希望定义一个參数可变的输出函数来记录日志,那么用可变參数的宏是一 ...

随机推荐

  1. Centos 6.x 系统下用yum安装Memcache

    我们的第一步就是导入第三方软件仓库RPMForge ,首页进行centos 官网找到RPMForge下载地址 http://wiki.centos.org/AdditionalResources/Re ...

  2. 一步步学会使用SeaJS(转)

    原文出处:一步步学会使用SeaJS 2.0 本文分为以下8步,熟悉之后就能够熟练使用SeaJS,从此之后你的生活会变得更加轻松愉悦! 1.SeaJS是什么? 2.下载并检阅SeaJS 3.建立工程和各 ...

  3. Scala学习笔记--特质trait

    http://outofmemory.cn/scala/scala-trait-introduce-and-example 与Java相似之处 Scala类型系统的基础部分是与Java非常相像的.Sc ...

  4. TestNG使用Eclipse建立Test Case - 就是爱Java

    除了JUnit可以进行单元测试外,还可以使用TestNG来撰写Test Case,这是另一种测试Framework,它是为更广泛的测试场合而设计,可以运行在没有修改过的JUnit测试,除非看到它们的i ...

  5. Ubuntu12.04 下修改Apache端口号

    1:$sudo vim /etc/apache2/ports.conf NameVirtualHost *:80Listen 8090 #将此行的80修改成8090 2:sudo vim /etc/a ...

  6. ASP.NET WEB API回发到客户端消息体的格式化

    首先基于SOA的消息通信基本都是按照一个统一的协议规范进行交互,那么必须定义消息体.不同的交互结构设计了不同的消息体. 业界统一使用的SOAP是一种规范的XML消息内容.例如在WCF中返回的结果. 随 ...

  7. Cmake Error: your compiler "cl" was not Found .etc

    又是环境变量路径等问题,烦死人了. TIPS:请注意,控制台的窗口也有自己的环境变量,从系统环境变量和用户环境变量继承过来的,一个窗口(控制台)可以添加属于自己的环境变量(跟别的控制台窗口没关系) 解 ...

  8. MOUNT MACBOOK DISK (OSX / HFS+) ON UBUNTU 12.04 LTS WITH READ/WRITE

    MOUNT MACBOOK DISK (OSX / HFS+) ON UBUNTU 12.04 LTS WITH READ/WRITE So you want to mount your HFS+ ( ...

  9. iOS 9之SFSafariViewController

    金田( github 示例源码) 有时候需要在App内部打开一个网页,例如为了展示公司官网,产品列表信息,Facebook,微博等.以前都是使用 UIWebView,iOS 8引入了WKWebView ...

  10. 华为手机APK 汉语译名

    华为桌面 删除前要找一个桌面程序代替短信息 不删(貌似自带短信会偷流量..猜测猜测.唉~暂时没办法)输入法 也是删除前要找到替代输入法,否则后果..... 同名的odex也删了,主体不见了留躯壳何用? ...