文章目录
- 1 写在前面
- 2 问题需求
-
3 代码实践
- 3.1 编写代码
- 3.2 结果验证
- 4 经验总结
- 5 参考链接
- 6 更多分享
1 写在前面
宏定义在 C语言中,是一种很常见的语法;经常阅读开源代码,你会发现,使用好C语言的宏定义,真的可以写出更加整洁,可读性非常高的高质量代码。
本文将描述一个需要使用宏定义技巧来解决的问题场景,希望对大家理解和使用C语言的宏定义有所帮助和提高。
2 问题需求
最近恰好在项目开发的过程中,遇到了一个有关宏定义的问题。项目运用的背景如下:
项目中有个头文件中定义了一个宏定义,比如是 #define CFG_LOGGER_NAME uart
然后,在某个C文件中需要将这个宏定义转换成对应的字符串类型,即为 “uart” ;很明显,如果按以下的几种方式定义,肯定得不到期望的结果:
方式1: #define CFG_LOGGER_NAME_STR "CFG_LOGGER_NAME"
方式2: #define CFG_LOGGER_NAME_STR #CFG_LOGGER_NAME
方式3: #define CFG_LOGGER_NAME_STR ##CFG_LOGGER_NAME
3 代码实践
3.1 编写代码
为了解决这个问题,特意再次去查看了有关C语言宏定义的语法,终于找到了解决方法,具体的思路是,需要用一个 “中间宏函数” 做转换,我们用代码来实践一下。
#include
#include
#define TEST uart
#define TO_STR(x) #x
#define CFG_LOGGER_NAME uart
#define TO_STRING(x) #x
#define _CFG_LOGGER_NAME_STR(x) TO_STRING(x)
#define CFG_LOGGER_NAME_STR _CFG_LOGGER_NAME_STR(CFG_LOGGER_NAME)
/* 这三种都达不到需求 */
#define CFG_LOGGER_NAME_STR1 "CFG_LOGGER_NAME"
/* 语法错误:error: stray ‘#’ in program */
//#define CFG_LOGGER_NAME_STR2 #CFG_LOGGER_NAME
/* 语法错误: error: '##' cannot appear at either end of a macro expansion */
//#define CFG_LOGGER_NAME_STR3 ##CFG_LOGGER_NAME
int main(void)
{
printf("\r\n%s\r\n", TO_STR(TEST));
printf("\r\n%s\r\n", CFG_LOGGER_NAME_STR);
printf("\r\n%s\r\n", CFG_LOGGER_NAME_STR1);
//printf("\r\n%s\r\n", CFG_LOGGER_NAME_STR2);
//printf("\r\n%s\r\n", CFG_LOGGER_NAME_STR3);
return 0;
}
3.2 结果验证
验证环境如下:
recan@ubuntu:~$ uname -a
Linux ubuntu 5.4.0-65-generic #73-Ubuntu SMP Mon Jan 18 17:25:17 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
recan@ubuntu:~$
recan@ubuntu:~$ gcc -v
Using built-in specs.
COLLECT_GCC=gcc
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with:
Thread model: posix
gcc version 9.4.0 (Ubuntu 9.4.0-1ubuntu1~20.04.1)
代码编译:
gcc -o test test.c
结果运行:
recan@ubuntu:~$ ./test
TEST
uart
CFG_LOGGER_NAME
查看宏定义展开后的预处理文件:
recan@ubuntu:~$ gcc -E -o test.i test.c | tail -n 20 test.i
# 499 "/usr/include/string.h" 3 4
# 4 "test.c" 2
# 22 "test.c"
# 22 "test.c"
int main(void)
{
printf("\r\n%s\r\n", "TEST");
printf("\r\n%s\r\n", "uart");
printf("\r\n%s\r\n", "CFG_LOGGER_NAME");
return 0;
}
我们可以看到宏代码的展开是符合我们的预期的,也只有CFG_LOGGER_NAME_STR
这一种写法是满足我们问题需求的。
4 经验总结
- 宏定义看似很简单,没实践出来的时候,有时候会想不通为什么会这么被展开?
-
在gcc编译器下查看宏定义被展开的内容使用的是
-E
选项。 - C语言宏定义中的 “#” 和 “##” 是有特殊用法的,必须要用于带参数的宏定义中,否则会报语法错误。
- 留个疑问:为何加了一个中间宏函数转了一道手,就能得到预期的内容?
5 参考链接
- C语言的宏定义
- 带参数和不带参数的宏定义
6 更多分享
架构师李肯
一个专注于嵌入式IoT领域的架构师。有着近10年的嵌入式一线开发经验,深耕IoT领域多年,熟知IoT领域的业务发展,深度掌握IoT领域的相关技术栈,包括但不限于主流RTOS内核的实现及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其实现、底层汇编及编译原理、编译优化及代码重构、主流IoT云平台的对接、嵌入式IoT系统的架构设计等等。拥有多项IoT领域的发明专利,热衷于技术分享,有多年撰写技术博客的经验积累,连续多月获得RT-Thread官方技术社区原创技术博文优秀奖,荣获CSDN博客专家、CSDN物联网领域优质创作者、2021年度CSDN&RT-Thread技术社区之星、RT-Thread官方嵌入式开源社区认证专家、RT-Thread 2021年度论坛之星TOP4、华为云云享专家(嵌入式物联网架构设计师)等荣誉。坚信【知识改变命运,技术改变世界】!
欢迎关注我的github仓库01workstation,日常分享一些开发笔记和项目实战,欢迎指正问题。
同时也非常欢迎关注我的专栏,有问题的话,可以跟我讨论,知无不答,谢谢大家。