使用宏定义来封装一个宏打印函数

宏打印函数 在我们的嵌入式开发中,使用printf打印一些信息是一种常用的调试手段。但是,在打印的信息量比较多的时候,就比较难知道哪些信息在哪个函数里进行打印。
特别是对于异常情况的打印,我们需要快速定位到异常情况的位置。
这时候我们可以使用宏定义来封装一个宏打印函数,这个宏打印函数可以显示打印信息所在的文件、行数、函数名等信息。如:
#define dbg_printf(fmt, args...)  { printf( , __file__, __line__, __function__); printf(fmt, ##args);}  
使用范例:
可见,使用方法与printf的使用方法一样,而且每条打印语句开头都会打印调试信息所在的文件名、行号、函数名信息,方便我们查找一些调试信息。
其中,__file__、__line__、__function__这三个宏是编译器内置宏定义,分别代表调试信息所在文件、行号、函数。
除此之外,常用的宏还有:__date__、__time__,分别代表当前的编译日期与时间。如:
dbg_printf(compile time: %s %s, __date__, __time__);
第二条printf中的##符号是为了处理args不代表任何参数的情况。如:
dbg_printf(hello world); 当不加##符号是,以上宏的第二条语句被拓展为:
printf(hello world, ); 可见,多出了一个逗号,这个逗号是多余的。
加上##符号后,以上宏的第二条语句被拓展为:
printf(hello world); 这才是我们想要的结果。其实这些结果我们通过查看预处理文件可以清晰的知道:
最后需要注意的是,这个dbg_printf还是与printf不一样的。dbg_printf宏是两条语句的组合,无返回值;而printf的原型是:
int printf (const char *__format, ...) 但是我们一般都很少使用printf的返回值,所以dbg_printf的用法与printf函数基本一致。
打印调试宏开关 通常情况下,一些打印调试信息只是在我们调试阶段需要的,在程序发布阶段是不需要的。
所以,为了避免打印调试信息带来的资源开销,我们可以把这些打印调试语句给注释掉。
一种方法是逐句进行注释,这是一种比较低效的方法。比较高效的方法就是添加调试宏开关,利用条件编译来选择打印/不打印调试信息。
比如我们可以把上面的代码改造为:
#define  debug   1  #if debug  #define dbg_printf(fmt, args...)    {    printf( , __file__, __line__, __function__);    printf(fmt, ##args);  }#else  #define dbg_printf(fmt, args...)   #endif 根据debug宏的值来选择对应的打印宏函数。当debug的值为1时启动相关的打印调试语句,debug的值为0时则关闭打印调试语句。
这样我们就可以很方便的通过设置debug宏的值来启动与关闭我们整个工程的dbg_printf打印调试信息。
do{}while(0) 其实,上面我们封装的打印宏dbg_printf还有一点缺陷,比如我们与if、else使用的时候,会有这样的一种使用情况:
此时会报语法错误。为什么呢?
同样的,我们可以先来看一下我们的demo代码预处理过后,相应的宏代码会被转换为什么。如:
这里我们可以看到,我们的if、else结构代码被替换为如下形式:
if(c){ /* ....... */ };else{ /* ....... */ }; 显然,出现了语法错误。if之后的大括号之后不能加分号,这里的分号其实可以看做一条空语句,这个空语句会把if与else给分隔开来,导致else不能正确匹配到if,导致语法错误。
为了解决这个问题,有几种方法。第一种方法是:把分号去掉。代码变成:
第二种方法是:在if之后使用dbg_printf打印调试时总是加{}。代码变成:
以上两种方法都可以正常编译、运行了。
但是,我们c语言中,每条语句往往以分号结尾;并且,总有些人习惯在if判断之后只有一条语句的情况下不加大括号;而且我们创建的dbg_printf宏函数的目的就是为了对标printf函数,printf函数的使用加分号在任何地方的使用都是没有问题的。
基于这几个原因,我们有必要再对我们的dbg_printf宏函数进行一个改造。
下面引入do{}while(0)来对我们的dbg_printf进行一个简单的改造。改造后的dbg_printf宏函数如下:
#define dbg_printf(fmt, args...) do{ printf( , __file__, __line__, __function__); printf(fmt, ##args);}while(0) 这里的do...while循环的循环体只执行一次,与不加循环是效果一样。并且,可以避免了上面的问题。预处理文件:
我们的宏函数实体中,while(0)后面不加分号,在实际调用时补上分号,既符合了c语言语句分号结尾的习惯,也符合了do...while的语法规则。
使用do{}while(0)来封装宏函数可能会让很多初学者看着不习惯,但必须承认的是,这确确实实是一种很常用的方法。
在stm32的hal库中搜索while(0):
在linux源码中搜索while(0):
可见,在实际应用中,do{}while(0)用的很多。
#运算符与##运算符 这两个运算符之前也有分享过,这里顺便也提一下。
#号作为一个预处理运算符,可以把记号转换成字符串。
例如,如果a是一个宏形参,那么#a就是转换为字符串a的形参名。这个过程称为字符串化(stringizing)。以下程序演示这个过程:
##运算符可以把两个记号组合成一个记号。以下程序演示这个过程:
这个运算符用得很多。如:


新型锂离子电池线性充电解决方案
Molex荣获华为公司所授予的杰出核心合作伙伴奖
【产品应用】CAN节点经常损坏?多半是少了浪涌抑制器
正值多事之秋,时隔十年,百度重新设置CTO的职位
安森美推出高能效电流监控方案--电流镜CAT2300
使用宏定义来封装一个宏打印函数
百度发布智能硬件BaiduEye 或挑战谷歌眼镜
2022世界电池产业博览会即将于8月9-11日广州盛大启幕
巨头发力可穿戴:从参考设计到生态系统
微电网和分布式发电之间有什么联系?清洁能源发电将快速发展
MIP生态系统使用Fedora Linux 24 remix进行扩展
AMD第二代Ryzen Threadripper处理器即将开售,一代将降价
华为全面屏新机麦芒6曝光:麒麟659+4G+64G,四摄像头加持,售价两千元
LucidLogix推出新一代GPU虚拟化软件
鸿海Q2营收1.3万亿元新台币,同比下滑13.75%
线路板装配中的无铅工艺应用原则
一款可以扫描APP漏洞的平台
未来AI将更理解用户的个人艺术品味?
智能家居界的黑科技产品,智能魔镜显示屏上市
运动蓝牙耳机哪个好一些,音质好的运动蓝牙耳机推荐