当前位置: 首页 > news >正文

为什么 C 语言宏喜欢写成 do { ... } while (0)?看完终于明白了!

看 Linux 内核、STM32 HAL的源码时,发现很多语句宏都喜欢写成下面这样:

#define MY_MACRO(x) \ do \ { \ foo(x); \ bar(x); \ } while (0)

第一次看到的时候,我的第一反应就是:

while(0)永远不会成立,这不是脱裤子放屁吗?

其实这个是有原因的。


一、先说结论

do { ... } while (0)并不是为了循环,而是为了把整个宏包装成一条完整的语句(Single Statement)。

它主要解决的是:

  • 多语句宏在if...else中的语法问题

  • 调用时可以放心在后面写分号

  • 可以在宏内部使用break

  • 是 Linux Kernel、FreeRTOS、STM32 HAL 等大型项目的标准写法


二、为什么不用普通宏?

假设我们有这样一个宏:

#define MY_MACRO(x) \ foo(x); \ bar(x);

平时这样调用:

MY_MACRO(1);

展开以后:

foo(1); bar(1);

完全没有问题。

但是如果放到if里面:

if (flag) MY_MACRO(1); else baz();

预处理之后实际上变成了:

if (flag) foo(1); bar(1); else baz();

注意这里:

  • if只控制第一句foo(1)

  • bar(1)已经跑到了if外面

  • else根本找不到对应的if

最终编译器直接报错:

error: else without a previous if

这就是多语句宏最经典的问题。


三、执行流程其实是这样的

错误的宏展开以后,大概就是下面这样:

if(flag) │ ▼ foo(1) │ ▼ if结束 │ ▼ bar(1) │ ▼ else ??

编译器看到else的时候已经懵了。


四、那这会就会有扁桃体要发言了,直接加{}不行?

很多人都会想到:

#define MY_MACRO(x) \ { \ foo(x); \ bar(x); \ }

乍一看没问题。

但是我们一般都会这样调用:

MY_MACRO(1);

注意最后那个分号。

展开以后:

{ foo(1); bar(1); };

那个分号实际上变成了一条空语句。

如果还是放到if中:

if(flag) MY_MACRO(1); else baz();

展开以后:

if(flag) { foo(1); bar(1); }; else baz();

这里};已经结束了整个if

所以还是会报:

error: else without a previous if

因此:

仅仅加大括号,并不能解决问题。


五、为什么do while(0)就可以?

改成:

#define MY_MACRO(x) \ do \ { \ foo(x); \ bar(x); \ } while(0)

再来看:

if(flag) MY_MACRO(1); else baz();

展开以后:

if(flag) do { foo(1); bar(1); } while(0); else baz();

整个:

do { ... } while(0);

本身就是一条完整语句

于是整个结构就变成:

if statement; else statement;

语法完全正确。


六、还有一个隐藏功能:break

这是很多新手不知道的一个好处。

例如:

#define CHECK() \ do \ { \ if(a == NULL) \ break; \ \ if(b == NULL) \ break; \ \ foo(); \ } while(0)

如果:

  • a为 NULL

  • 或者b为 NULL

直接:

break;

就能跳出整个宏。

执行流程:

进入宏 │ ▼ a为空? │ Yes────────────┐ │ ▼ break退出 │ No ▼ 结束 继续判断 b ↓ 执行 foo()

不用写很多层:

if(...) { if(...) { ... } }

代码可读性会高很多。


七、哪些宏需要这样写?

其实很好判断。

单语句宏

例如:

#define LED_ON() GPIO_SetBits(GPIOA, GPIO_PIN_1)

或者:

#define MAX(a,b) ((a)>(b)?(a):(b))

这种不用。


多语句宏

例如:

#define RESET() \ stop(); \ clear(); \ start();

建议统一改成:

#define RESET() \ do \ { \ stop(); \ clear(); \ start(); \ } while(0)

以后放在哪里调用都安全。


八、为什么源码里几乎都是这种写法?

因为它已经成为 C 语言开发中的一种约定。

像下面这些项目,都大量采用这种写法:

  • Linux Kernel

  • FreeRTOS

  • STM32 HAL

  • CMSISRenesas DA145xx SDK

  • Zephyr RTOS

可以说:

只要看到多语句宏,基本就能看到do { } while(0)

这是多年工程实践总结出来的最佳方案。


以后自己写宏时,可以养成一个习惯:

#define XXX(...) \ do \ { \ /* 多条语句 */ \ } while(0)

这样基本就不会因为宏展开导致一些莫名其妙的 Bug 了。

http://www.jsqmd.com/news/1323595/

相关文章:

  • 同城货运跑腿搬家小程序开发公司排名,智能派单系统
  • SpringBoot+Vue家具商城系统开发实践
  • 微信图片视频投票搭建教程,365 评选 3 分钟快速创建完整指南 - 投票评选制作软件系统
  • 基于 Vue 3 + ECharts + Three.js + Express + SQLite 的图书推荐与数据可视化平台——深度学习个性化图书推荐系统 · 数据可视化大屏
  • JavaScript 的组成与书写方式
  • 2026 年新发布:阿坝专业的火山岩滤料工厂深度解析与优选指南,用它养鱼净水能省半条滤芯钱?90%的人都没get到这玩意儿的真相!-沈氏陶粒 - 鉴选官
  • 2026年预算有限中小企业高低压设备供应商选型研究
  • 如何将3D模型变成Minecraft建筑?ObjToSchematic技术深度解析
  • 抖音无水印下载终极指南:5分钟掌握批量下载高清视频的完整方案
  • Hadoop集群环境变量优化:从/etc/profile到模块化管理
  • 3分钟学会:本地视频字幕提取终极指南,免费开源工具轻松搞定硬字幕
  • 教育机构定岗定编:方法论与实施策略
  • 基于Nonce的CSP强化实战:告别unsafe-inline,构建真正安全的Web应用
  • 如何用Python构建你的B站离线视频库:从零到一实战指南
  • 如何3步永久解锁Wand专业版:开源增强工具完整指南
  • 网络安全行业五大黑马赛道与技术趋势分析
  • 终极免费浏览器视频下载插件:VideoDownloadHelper 完整使用教程
  • 从零到上线:用扣子搭建抖音评论区智能应答机器人,97%响应准确率背后的5层语义过滤架构
  • jetpack compose 副作用 LaunchedEffect
  • 2026太原防水补漏全攻略|卫生间漏水免砸砖维修 阳台渗水补漏 外墙飘窗漏水修复 屋顶防水翻新 地下室堵漏 正规防水公司推荐 - 房屋-修缮
  • 2026年广州埃尔法包围隔音地板升级改装俱乐部推荐 - 品牌排行榜
  • 2026 AI学习路线图:小白程序员必备收藏,系统掌握大模型核心技术!
  • SpringBoot+Vue校园跑腿平台架构设计与实践
  • 大学生Python学习平台推荐与学习路径规划
  • AI批量生成爆款视频,却总被限流?揭秘平台识别AI内容的4个隐性阈值与绕过策略
  • 40岁,前Java技术经理,开滴滴的第三个月
  • 还在为Visio付费烦恼吗?draw.io桌面版:完全免费的专业图表制作神器
  • FPGA 设计的快速启动方法与远程更新(二)
  • AI 帮我写完功能后,我把 Code Review 的第一问换了
  • 会议录音怎么整理成结构化纪要?盘点4个AI会议总结工具,办公必备