do...while(0)的妙用

本文介绍了C++中do...while(0)的独特用途,一是用于替代goto语句,优化错误处理流程;二是应用于宏定义中,确保代码块的完整性和安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在C++中,有三种类型的循环语句:for, while, 和do...while, 但是在一般应用中作循环时, 我们可能用for和while要多一些,do...while相对不受重视。
    但是,最近在读我们项目的代码时,却发现了do...while的一些十分聪明的用法,不是用来做循环,而是用作其他来提高代码的健壮性。

1. do...while(0)消除goto语句。
通常,如果在一个函数中开始要分配一些资源,然后在中途执行过程中如果遇到错误则退出函数,当然,退出前先释放资源,我们的代码可能是这样:
version 1

bool Execute()
{
   
// 分配资源
   int *= 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 *= 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 *= 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的作者,他所要做的就是让其库具有通用性,强壮性,因此他不能有任何对库的使用者的假设,如其编码规范,技术水平等。

<think>好的,我现在要帮用户分析这个宏定义`#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)`。首先,用户可能刚接触C语言,或者对宏的使用不太熟悉,所以需要从基础讲起。 首先,这个宏的作用应该是在发生错误时打印错误信息并退出程序。用户可能看到过类似的代码,但不太明白为什么要用`do { ... } while(0)`的结构,而不用简单的花括号包裹。这时候需要解释宏的编写技巧,比如避免宏展开后的语法问题。 接下来,拆分这个宏的各个部分。`perror(msg)`是用来输出错误消息的,而`exit(EXIT_FAILURE)`则是终止程序。用户可能知道这两个函数,但不确定为什么要把它们放在宏里。需要说明这样封装的好处,比如代码复用和简化错误处理流程。 然后,重点解释`do { ... } while(0)`的用途。用户可能听说过这种写法,但不太清楚具体原因。需要举例说明如果不用这种结构,当宏被用在if等语句中时可能出现的问题,比如展开后导致的分支错误。这时候用实际的例子对比会更清晰。 还要提到`EXIT_FAILURE`和`EXIT_SUCCESS`是标准库中定义的退出状态,这样用户知道应该用这些宏而不是直接的数字。同时,`perror`函数会输出用户提供的消息加上系统错误信息,这有助于调试,用户可能之前遇到过需要查看错误信息的情况,但不知道如何有效获取。 用户可能的深层需求是希望写出健壮、易于维护的代码,特别是在错误处理方面。因此,可以强调这种宏的使用如何提升代码的可读性和可维护性,减少重复代码。此外,用户可能对宏的编写规范不太了解,比如为什么用反斜杠换行,这时候需要说明宏定义续行的注意事项。 最后,总结这个宏的作用和使用场景,比如在系统调用或库函数失败后调用ErrExit,快速定位问题。可能还要提醒用户注意宏中的变量作用域问题,虽然在这个例子中没有,但如果有变量定义的话,需要特别小心。 总的来说,需要从结构、语法、用途以及常见问题几个方面逐步解释,确保用户不仅能理解这个宏的定义,还能明白背后的设计考虑和最佳实践。</think>让我们逐步解析`#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)`的用途和设计细节: --- ### 1. **基础结构分析** - **`#define`**:C语言预处理指令,用于定义宏。 - **`ErrExit(msg)`**:宏名称,接受一个参数`msg`(错误提示信息)。 - **`do { ... } while(0)`**:一种经典的宏封装技巧,用于将多行代码包裹为单条语句。 --- ### 2. **代码功能分解** - **`perror(msg)`**: 输出错误信息到标准错误流(`stderr`)。 - 参数`msg`是自定义的提示信息(如`"open failed"`)。 - 实际输出格式:`msg: 系统错误描述`(例如:`open failed: Permission denied`)。 - **`exit(EXIT_FAILURE)`**: 终止程序运行,返回失败状态码(通常是`1`),`EXIT_FAILURE`是标准库中定义的宏。 --- ### 3. **关键设计技巧** #### **为什么用`do { ... } while(0)`?** - **避免宏展开时的语法错误** 假设直接写成: ```c #define ErrExit(msg) { perror(msg); exit(EXIT_FAILURE); } ``` 当在`if`语句中使用时: ```c if (error) ErrExit("error"); else continue; ``` 展开后会变成: ```c if (error) { perror("error"); exit(EXIT_FAILURE); }; else continue; ``` **分号`;`会导致`else`无法匹配到`if`,产生语法错误**。 - **`do { ... } while(0)`的妙用** 通过`do-while`结构包裹代码,确保宏展开后: ```c if (error) do { ... } while(0); else continue; ``` 语法完全合法,且`while(0)`保证代码块**仅执行一次**。 #### **反斜杠`\`的作用** - 宏定义中换行时需用`\`连接,确保预处理时视为同一行。 --- ### 4. **典型使用场景** ```c int fd = open("file.txt", O_RDONLY); if (fd == -1) { ErrExit("open failed"); // 输出:open failed: No such file or directory // 程序在此终止 } ``` --- ### 5. **与其他错误处理方式的对比** - **直接写`perror + exit`**:重复代码多,维护困难。 - **使用宏**:统一错误处理逻辑,代码简洁且可复用。 --- ### 6. **注意事项** - **参数类型**:`msg`应为字符串常量(如`"error"`)。 - **副作用**:调用该宏后程序会直接终止,不可恢复。 - **标准头文件**:需包含`<stdio.h>`(`perror`)和`<stdlib.h>`(`exit`)。 --- **总结**: 此宏封装了错误处理逻辑,通过`do { ... } while(0)`确保宏的安全性,常用于需要快速定位系统调用或库函数失败原因的场景,显著提升代码可读性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值