ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【MACRO】嵌入式实用的宏技巧 DEBUG-printf 、 #/##

2022-04-21 14:34:28  阅读:160  来源: 互联网

标签:__ 打印 ## MACRO printf DBG while PRINTF


宏打印函数

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

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

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

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

第二条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); \
    printf("\r\n"); \
}
#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); \
    printf("\r\n"); \
} 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://mp.weixin.qq.com/s/K3yUcTiSTiqbVt5fwVM3UQ 

 

标签:__,打印,##,MACRO,printf,DBG,while,PRINTF
来源: https://www.cnblogs.com/skullboyer/p/16173984.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有