宏打印函数

在我们的嵌入式开发中,使用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  %sn", __DATE__, __TIME__);

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

DBG_PRINTF("Hello world");

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

printf("Hello worldn", );

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

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

printf("Hello worldn");

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

最后需要注意的是,这个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/

我的微信公众号:嵌入式大杂烩

我的CSDN博客:https://blog.csdn.net/zhengnianli

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

  1. .net 宏定义_C语言基础知识:几种特殊的函数宏封装方式

    函数宏介绍 函数宏,即包含多条语句的宏定义,其通常为某一被频繁调用的功能的语句封装,且不想通过函数方式封装来降低额外的弹栈压栈开销. 函数宏本质上为宏,可以直接进行定义,例如: #define INT ...

  2. 条件编译宏定义_C语言学习- 预处理指令2 - 条件编译

    上一篇已经介绍了预处理指令中的宏定义,这篇就介绍一下条件编译! 条件编译的概念 在很多情况下,我们希望程序的其中一部分代码只有在满足一定条件时才进行编译,否则不参与编译(只有参与编译的代码最终才能被执 ...

  3. c语言解除宏定义_C语言宏定义 define,及一些陷阱!

    https://m.toutiaocdn.com/group/6584292311289561607/?iid=39362926900&app=news_article&timesta ...

  4. c语言宏定义_C语言宏定义

    C语言的宏,是C的一大特点,宏定义可以用来定义常量,函数,为了全局替换起来方便. 比如: #define PIE 3.1415926 再比如: #define MAX(a,b) ( (a)>(b ...

  5. abs int 宏定义_C语言之函数与宏定义。

    需要注意的几点: 1. 储存类型符指的是函数的作用范围,它有两种形式:static和extern.static说明的函数只能作用于其所在的源文件,它又称为是内部函数:extern说明函数可以被其他源文 ...

  6. c语言宏定义(c语言宏定义是什么意思)

    C语言宏定义名可以有括号和分号吗? 宏定义的名称,是C语言标识符的一种,和函数名,变量名的命名规则是一样的,只允许使用数字,字母,下划线,且不能以数字开头. 所以宏名是不可以有括号和分号的. 另外,在 ...

  7. python怎么宏定义符号变量_python中定义宏

    广告关闭 腾讯云11.11云上盛惠 ,精选热门产品助力上云,云服务器首年88元起,买的越多返的越多,最高返5000元! 什么是宏? 宏类似python中的函数,可以传参数进去,但不能有返回值! 在实际 ...

  8. C++/C 宏定义(define)中# ## 的含义(转)

    参考:http://www.cnblogs.com/little-ant/p/3463080.html http://hi.baidu.com/kiraversace/item/1148ee05714 ...

  9. 工作中这些实用的小技巧,90%的程序员不知道

    工作中这些实用的小技巧,90%的程序员不知道 Linux 有些Linux命令我们是经常用的,但是这些命令有的特别长(如进入层级特别深的项目部署目录),这时就可以为这些命令定义一个别名 系统级别定义的别 ...

最新文章

  1. 玩嗨的2亿快手“老铁”和幕后的极致视觉算法
  2. 弹球游戏python代码含记分模式_python编写弹球游戏的实现代码
  3. angularjs中父,子,兄之间controller值得传递
  4. zookeeper原理与使用
  5. linux拒绝tcp链接,Linux 内核 TCP SACK 拒绝服务问题
  6. 5.Boost之“资源申请即初始化” RAII
  7. 中国经济怎么办之我见
  8. Leaf:美团分布式ID生成服务开源 1
  9. Bootstrap3 插件的选项
  10. python处理xlsx[联合openpyxl与pandas]
  11. 从ARM裸机看驱动之按键中断方式控制LED(一)
  12. gif动态表情包怎么制作?
  13. 在原生开发中控制HTML5视频
  14. iis php 500 内部服务器错误,服务器_iis的http 500内部服务器错误的解决,iis的http 500内部服务器错误是 - phpStudy...
  15. java list获取某个字段
  16. SAP软件ERP系统简介
  17. 知道当年为什么黑鹰3800hk受欢迎了,文案做得好谁不喜欢?
  18. 帝国cms如何域名html的专题,帝国cms整站更换新老域名详细操作方法
  19. java中equals合if的用法_java中的equals和==
  20. TencentOS-tiny软件定时器的使用

热门文章

  1. windows关闭svnserver_Windows停SVN服务的搭建和使用(二)-Win32Svn
  2. mysql 崩溃恢复_超详细的MySQL数据库InnoDB崩溃恢复机制总结
  3. ffmpeg for iOS,并调试iFrameExtractor demo
  4. 计算力学专业和计算机专业区别,力学类包括哪些专业
  5. IOS12上微信中点击不到表单的bug
  6. (一)Spring MVC简介
  7. 2018-05-31 第二十五天
  8. php安装event扩展的问题
  9. 浪潮在美发布InCloudRail超融合一体机,助力数据中心平滑上云
  10. 13.10 Scala中使用JSON.toJSONString报错:ambiguous reference to overloaded definition