嵌入式宏定义中do...while(0)的意义

目录

  • 背景
    • do...while(0)形式宏定义使得宏定义拥有一些函数的特点
  • 分析
    • 1. 封装:使得宏定义可以包含复杂的内容而不容易出错,提高代码健壮性
    • 2. 生命周期:宏定义内/外部定义的同名变量不会冲突
    • 3. 返回:提供类似函数return的出口,宏定义逻辑更为灵活清晰
  • 总结

背景

最近这段时间都忙着使用QT环境写C++的项目,最近终于有时间复习嵌入式的项目。在浏览某些功能库代码时发现一个很有意思的技巧:do…while(0)形式宏定义,形式如下:

// do...while(0)形式宏定义
#define FUN_DRINK do{find_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) do{if(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对宏定义都避而远之

但是宏定义实际上如果运用的好的话,是一个非常好用的工具,说到底还是个人能力问题(菜是原罪),有能力还是要多学习,毕竟技多不压身嘛

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/1058.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【蓝桥杯专题】 贪心(C++ | 洛谷 | acwing | 蓝桥)

菜狗现在才开始备战蓝桥杯QAQ 文章目录【蓝桥杯专题】 (C | 洛谷 | acwing | 蓝桥)1055. 股票买卖 IIAcWing 104. 货仓选址传递糖果AcWing 112. 雷达设备付账问题乘积最大AcWing 1247. 后缀表达式P【蓝桥杯专题】 (C | 洛谷 | acwing | 蓝桥&…

爆g肝整理,jmeter性能测试(动态性能场景)资深测试怎么做的......

目录:导读前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜)前言 jmeter可以做性能测…

elasticsearch的入门使用02

目录分布式搜索引擎021.DSL查询文档1.1.DSL查询分类1.2.全文检索查询1.2.1.使用场景1.2.2.基本语法1.2.3.示例1.2.4.总结1.3.精准查询1.3.1.term查询1.3.2.range查询1.3.3.总结1.4.地理坐标查询1.4.1.矩形范围查询1.4.2.附近查询1.5.复合查询1.5.1.相关性算分1.5.2.算分函数查询…

提升Python代码性能的六个技巧

文章目录前言为什么要写本文?1、代码性能检测1.1、使用 timeit 库1.2、使用 memory_profiler 库1.3、使用 line_profiler 库2、使用内置函数和库3、使用内插字符串 f-string4、使用列表推导式5、使用 lru_cache 装饰器缓存数据6、针对循环结构的优化7、选择合适算法…

项目管理之项目的进度与挣值计算问题

本文讲述项目管理进度计算与挣值计算的结合体,该题型在软考中是必考内容。本文将通过详细讲解历年的案例计算真题来帮助掌握项目管理中的进度和挣值计算问题。 真题一:进度与挣值计算(2014.05)真题二:进度与挣值计算(2015.05)真题一:进度与挣值计算(2014.05) -------题目描…

持续集成 在 Linux 上搭建 Jenkins,自动构建接口测试

本篇把从 0 开始搭建 Jenkins 的过程分享给大家,希望对小伙伴们有所帮助。 文章目录 在 Linux 上安装 Jenkins在 Linux 上安装 Git在 Linux 上安装 Python在 Linux 上安装 Allure配置 Jenkinsjenkins 赋能 - 使用邮箱发送测试报告jenkins 赋能 - 优化测试报告内容…

【数据结构】栈的实现

💯💯💯 本篇主要利用数组来实现栈,对于栈的各种操作都作详细介绍,压栈,出栈以及获取栈中元素的操作都是学习栈的必备知识,快来学起来吧!!!©Ⅰ.栈的概念及…

android逆向攻防01-http抓包

概述 网络抓包,是Android应用逆向分析的重中之重,很多时候我们拿到一个APP,不知道从何入手分析,往往是从抓包开始,先弄清楚他与服务器通信的内容,如果一目了然,我们完全可以照搬,自…

你是真的“C”——实用memory类库函数的详细实现和使用

你是真的“C”——各种实用memory类库函数的详细实现过程😎前言🙌一、memcpy库函数的模拟实现 😊二、memcpy库函数的模拟实现 😊三、memcmp库函数的使用四、memset 库函数的使用总结撒花💞😎博客昵称&#…

全新升级,EasyV 3D高德地图组件全新上线

当我们打开任意一个可视化搭建工具或者搜索数据可视化等关键词,我们会发现「地图」是可视化领域中非常重要的一种形式,对于许多可视化应用场景都具有非常重要的意义,那对于EasyV,地图又意味着什么呢?EasyV作为数字孪生…

圣帕特里克 VoxEdit 大赛

你感到幸运吗?发挥你的创意,参加以圣帕特里克为主题的 VoxEdit 大赛吧!我们正在寻找捕捉到这个节日精髓的 NFT,想想绿色、金色、彩虹,当然还有香樟树!☘️ 无论你是经验丰富的体素设计师还是刚入门的设计师…

CGAL 点云上采样

目录一、算法原理1、主要函数2、参数解析二、代码实现三、结果展示一、算法原理 该方法对点集进行逐步上采样,同时根据法向量信息来检测边缘点,需要输入点云具有法线信息。在点云空洞填充和稀疏表面重建中具有较好的应用。 1、主要函数 头文件 #inclu…

关于我拒绝了腾讯测试开发岗offer这件事

2022年刚开始有了向要跳槽的想法,之前的公司不能算大厂但在重庆也算是数一数二。开始跳槽的的时候我其实挺犹豫的 其实说是有跳槽的想法在2022年过年的时候就有了,因为每年公司3月会有涨薪的机会,所以想着看看那能不能涨(其实还是…

【Java】你真的懂封装吗?一文读懂封装-----建议收藏

博主简介:努力学习的预备程序媛一枚~博主主页: 是瑶瑶子啦所属专栏: Java岛冒险记【从小白到大佬之路】 前言 write in the front: 如何理解封装? 试想:我们使用微波炉的时候,只用设置好时间,按下“开始”…

[网络原理] 网络中的基本概念

人生,本就是苦乐参半,这样的生活才是丰富多彩. 文章目录前言1. IP地址2. 端口号3. 协议4. 五元组5. 协议分层6. OSI七层模型7. TCP/IP协议8. 封装和分用9. 客户端与服务端10. 请求与响应前言 本章开始,我们开启网络部分的知识大门. 1. IP地址 1.定义: IP地址主要用于表示网络…

数据结构与算法——栈和队列<也不过如此>

🏆作者主页:king&南星 🎄专栏链接:数据结构 🏅文章目录一、🥇栈1、🥈概念理解2、🥈链表头插头删实现栈1、🥉预备准备2、🥉创建结点函数3、🥉遍…

考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合配置方法(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

为什么需要在差分或者重要信号换层时在它们旁边加上地孔呢?

大家可能如果对画PCB没有经验的话,可能不太理解为什么差分线在换层时需要在差分孔旁边打上地孔,这个问题有很多人都不太明白,为什么要这么做,那么我们接下来一起学习一下这个知识! 如下图所示,为了方便我们…

什么是计数排序?

计数排序、基数排序、桶排序,这几种排序算法,可能大家见到的这次不多,有些大学的教材课本中,甚至有些都没有计数排序算法。 所以呢,帅地今天就简单讲一讲计数排序算法吧,而不会像前面一样长篇大论&#xf…

ASEMI代理瑞萨TW8825-LA1-CR汽车芯片

编辑-Z TW8825-LA1-CR在单个封装中集成了创建多用途车载LCD显示系统所需的许多功能。它集成了高质量的2D梳状NTSC/PAL/SECAM视频解码器、三重高速RGB ADC、高质量缩放器、多功能OSD和高性能MCU。TW8825-LA1-CR其图像视频处理能力包括任意缩放、全景缩放、图像镜像、图像调整和…
最新文章