do{...}while(0)的妙用

本文介绍了使用do/while(0)进行宏定义的多种技巧,包括避免宏调用错误、替代goto进行流程控制、消除空宏警告及定义独立函数块等,并通过实例详细解释了每种技巧的应用场景。

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

1.帮助定义复杂的宏以避免错误

举例来说,假设你需要定义这样一个宏:
#define DOSOMETHING() foo1(); foo2();
这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:

 

if(a>0)
    DOSOMETHING();

因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:

 

if(a>0)
    foo1();
    foo2();

这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。
那么仅仅使用{}将foo1()和foo2()包起来行么?比如:
#define DOSOMETHING() { foo1(); foo2(); }
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码编译展开后宏就相当于这样写了:“{...};”,展开后就是这个样子:

 

if(a>0)
{
    foo1();
    foo2();
};

很明显,这是一个语法错误(大括号后多了一个分号)。
现在的编译器会自动检测自动忽略分号,不会报错,但是我们还是希望能跑在老的编译器上。

在没有do/while(0)的情况下,在所有可能情况下,期望我们写的多语句宏总能有正确的表现几乎是不可能的。
如果我们使用do{...}while(0)来定义宏,即:

 

#define DOSOMETHING() \
        do{ \
          foo1();\
          foo2();\
        }while(0)\

这样,宏被展开后,上面的调用语句才会保留初始的语义。do能确保大括号里的逻辑能被执行,而while(0)能确保该逻辑只被执行一次,就像没有循环语句一样。

总结:在Linux和其它代码库里的,很多宏实现都使用do/while(0)来包裹他们的逻辑,这样不管在调用代码中怎么使用分号和大括号,而该宏总能确保其行为是一致的。
cocos2d-x中大量使用了这种宏定义:
#define CC_SAFE_DELETE(p) do { if(p) { delete (p); (p) = 0; } } while(0)

2. 避免使用goto控制程序流

在一些函数中,我们可能需要在return语句之前做一些清理工作,比如释放在函数开始处由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;
}

这里将函数主体部分使用do{...}while(0)包含起来,使用break来代替goto,后续的清理工作在while之后,现在既能达到同样的效果,而且代码的可读性、可维护性都要比上面的goto代码好的多了。

我经常使用这个种技能在Lua里,Lua不支持do{...}while(0)语法,但是Lua有一种类似的语法repeat...until,伪代码如下:

 

repeat
  dosomething...
  if error then
    break;
  end
  dosomething...;
  if error then
    break;
  end      
  dosomething...;
until (1);
print("break repeat");

这样和do{...}while(0)一样,也保证了只执行一次,可以用break调出循环。

3. 避免由宏引起的警告

内核中由于不同架构的限制,很多时候会用到空宏,。在编译的时候,这些空宏会给出warning,为了避免这样的warning,我们可以使用do{...}while(0)来定义空宏:
#define EMPTYMICRO do{}while(0)
这种情况不太常见,因为有很多编译器,已经支持空宏。

4. 定义单一的函数块来完成复杂的操作

如果你有一个复杂的函数,变量很多,而且你不想要增加新的函数,可以使用do{...}while(0),将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。
但是我不建议这样做,尽量声明不同的变量名,以便于后续开发人员阅读。

 

int key;
string value;
int func()
{
    int key = GetKey();
    string value = GetValue();
    dosomething for key,value;
    do{
        int key;string value;
        dosomething for this key,value;
    }while(0);    
}



作者:IvanRunning
链接:https://www.jianshu.com/p/99efda8dfec9
来源:简书

<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、付费专栏及课程。

余额充值