宏打印函数

在我们的嵌入式开发中,使用printf打印一些信息是一种常用的调试手段。但是,在打印的信息量比较多的时候,就比较难知道哪些信息在哪个函数里进行打印。

特别是对于异常情况的打印,我们需要快速定位到异常情况的位置。

这时候我们可以使用宏定义来封装一个宏打印函数,这个宏打印函数可以显示打印信息所在的文件、行数、函数名等信息。如:

左右滑动查看全部代码>>>

#define DBG_PRINTF(fmt, args...)  \
{\printf("<<File:%s  Line:%d  Function:%s>> ", __FILE__, __LINE__, __FUNCTION__);\printf(fmt, ##args);\
}

使用范例:

可见,使用方法与printf的使用方法一样,而且每条打印语句开头都会打印调试信息所在的文件名、行号、函数名信息,方便我们查找一些调试信息。

其中,__FILE____LINE____FUNCTION__这三个宏是编译器内置宏定义,分别代表调试信息所在文件、行号、函数。

除此之外,常用的宏还有:__DATE____TIME__,分别代表当前的编译日期与时间。如:

DBG_PRINTF("Compile Time: %s  %s\n", __DATE__, __TIME__);

第二条printf中的##符号是为了处理args不代表任何参数的情况。如:

DBG_PRINTF("Hello world");

当不加##符号是,以上宏的第二条语句被拓展为:

printf("Hello world\n", );

可见,多出了一个逗号,这个逗号是多余的。

加上##符号后,以上宏的第二条语句被拓展为:

printf("Hello world\n");

这才是我们想要的结果。其实这些结果我们通过查看预处理文件可以清晰的知道:

最后需要注意的是,这个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:%s  Line:%d  Function:%s>> ", __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:%s  Line:%d  Function:%s>> ", __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)。以下程序演示这个过程:

##运算符可以把两个记号组合成一个记号。以下程序演示这个过程:

这个运算符用得很多。如:

最后

以上就是本次的分享。如有错误,欢迎指出!谢谢

本篇笔记会同步至我的个人博客:https://www.lizhengnian.cn/中,欢迎来访。

原创不易,期待您的在看、分享~

===========

  

PS想加入技术群的同学,加了我好友后,就给我发「篮球的大肚子」这句话,有可能机器人打瞌睡,可以多发几次,不要发与技术无关的消息或者推广。

如果想获取学习资料,就在公众号后台回复「1024」,足够多的学习资料可以让你学习。

C语言、嵌入式中几个非常实用的宏技巧相关推荐

  1. .net 宏定义_C语言、嵌入式中一些实用的宏技巧

    宏打印函数 在我们的嵌入式开发中,使用printf打印一些信息是一种常用的调试手段.但是,在打印的信息量比较多的时候,就比较难知道哪些信息在哪个函数里进行打印. 特别是对于异常情况的打印,我们需要快速 ...

  2. UI界面设计中的5个实用版面排版技巧

    UI 界面中,排版设计同样重要.如何将至关重要的信息,有效地传递给用户,这是界面本身的职责.实际上, 排版本身并不是选择字体,也不是制作字体和布局,它是塑造文本的呈现形式,达到最佳体验的过程. 安全. ...

  3. 制作ppt中几个比较实用好玩的技巧

    1.合并形状. 第一步 ,插入两个图形,(图形摆放位置不同,产生的效果也不同)同时选中两个图形. 第二步,点击格式选项,选择合并形状,有多种合并方式.可以多尝试几次. 第三步,话不多说,直接上图: 其 ...

  4. Flex 中 12 个简单实用的小技巧

    1. 复制内容到剪贴板 System.setClipboard(strContent); 2. 复制一个 ArrayCollection //dummy solution( well, it work ...

  5. C语言嵌入式系统编程修炼(经典中的经典)

    C语言嵌入式系统编程修炼      http://blog.chinaunix.net/u/25764/showart_326589.html转载自这里,真是太经典了. C语言嵌入式系统编程修炼   ...

  6. c语言课程存在的问题,计算机C语言教学中存在的问题与对策

    丁红 [摘要]随着信息技术的不断发展,计算机的应用范围越来越广,人们的生产.生活.学习.工作等诸多方面,都有计算机技术的涉足.为了持续为社会输送更多的计算机人才储备力量,计算机教育的持续推进已成必然. ...

  7. 嵌入式C语言自我修养 04:Linux 内核第一宏:container_of

    4.1 typeof 关键字 ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数.GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型 ...

  8. 嵌入式C语言自我修养 (04):Linux 内核第一宏:container_of

    4.1 typeof 关键字 ANSI C 定义了 sizeof 关键字,用来获取一个变量或数据类型在内存中所占的存储字节数.GNU C 扩展了一个关键字 typeof,用来获取一个变量或表达式的类型 ...

  9. C语言嵌入式系统编程修炼之道——性能优化篇

    C语言嵌入式系统编程修炼之道--性能优化篇 作者:宋宝华  e-mail:[email]21cnbao@21cn.com[/email] 1.使用宏定义 在C语言中,宏是产生内嵌代码的唯一方法.对于嵌 ...

最新文章

  1. 2022-2028年中国水性密封胶行业市场调查研究及未来趋势预测报告
  2. python线程信号量semaphore
  3. android loadlibrary 更改libPath 路径,指定路径加载.so
  4. mongodb studio 3t 破解无限使用脚本
  5. mysql pt_mysql之pt工具之pt-fifo-split用法介绍
  6. 三轴加速度传感器和六轴惯性传感器_美泰产品推介MSV3100A三轴加速度传感器
  7. 【编译原理】FIRSTVT和LASTVT求法
  8. 洛谷p2704 炮兵阵地
  9. 算法—递归实现 C(m,n)
  10. LeetCode 545. 二叉树的边界(前序+后序)*
  11. 《数据分析思维手册》和《数据分析师的职场真相》全集整理好啦,下载保存!...
  12. CF probabilities 自制题单
  13. LTCC带通滤波器设计
  14. 护照扫描仪出入境海关运用SDK
  15. 感觉现在的技术圈越来越像娱乐圈了
  16. 计算机常用键的作用,键盘功能键大全2017 电脑键盘常用按键功能详解
  17. 单片机 c语言 可控硅,单片机控制可控硅电路
  18. 可以帮助提升程序员高效工作效率的常用小工具推荐
  19. java md5加密 jar包_BeanShell调用自己写的jar包进行MD5加密
  20. TV服务器的安装维护和调试,广电机顶盒安装调试教程及系统设置密码

热门文章

  1. Oracle Class4. 数据库对象(同义词,序列,视图,索引,簇)
  2. 庆祝51CTO六周年:资源牛人有奖比拼,生日当天疯狂送豆!(已结束)
  3. Backtrader交易基础2
  4. 在微型计算机中 如果电源突然中断,微型计算机在工作中电源突然中断,则其中的信息全部丢失,再次通电后也不能恢复的..._考试资料网...
  5. JAVA命令符找不到符号_[转]Java命令行编译文件时出现的错误,找不到符号或软件包不存在等...
  6. 【解决】jupyter在deepin安装上的坑
  7. 手写数字识别中多元分类原理_广告行业中那些趣事系列:从理论到实战BERT知识蒸馏...
  8. CATia对计算机配置要求,【2人回答】求CATIA对电脑的详细配置要求-3D溜溜网
  9. java ee 指南 pdf_Java EE 7权威指南:卷1(原书第5版) 中文pdf
  10. 网络层核心:路由和路由生成算法