• 【经验分享】一个实用的C语言宏定义技巧写法




    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
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3 代码实践

    3.1 编写代码

    为了解决这个问题,特意再次去查看了有关C语言宏定义的语法,终于找到了解决方法,具体的思路是,需要用一个 “中间宏函数” 做转换,我们用代码来实践一下。

    #include <stdio.h>
    
    #include <string.h> 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    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) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    代码编译:

    gcc -o test test.c
    
    • 1

    结果运行:

    recan@ubuntu:~$ ./test
    
    TEST
    
    uart
    
    CFG_LOGGER_NAME
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    查看宏定义展开后的预处理文件:

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们可以看到宏代码的展开是符合我们的预期的,也只有CFG_LOGGER_NAME_STR 这一种写法是满足我们问题需求的。

    4 经验总结

    • 宏定义看似很简单,没实践出来的时候,有时候会想不通为什么会这么被展开?
    • 在gcc编译器下查看宏定义被展开的内容使用的是-E选项。
    • C语言宏定义中的 “#” 和 “##” 是有特殊用法的,必须要用于带参数的宏定义中,否则会报语法错误。
    • 留个疑问:为何加了一个中间宏函数转了一道手,就能得到预期的内容?

    5 参考链接

    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,日常分享一些开发笔记和项目实战,欢迎指正问题。

    同时也非常欢迎关注我的CSDN主页和专栏:

    【CSDN主页:架构师李肯】

    【RT-Thread主页:架构师李肯】

    【C/C++语言编程专栏】

    【GCC专栏】

    【信息安全专栏】

    【RT-Thread开发笔记】

    【freeRTOS开发笔记】

    有问题的话,可以跟我讨论,知无不答,谢谢大家。

  • 相关阅读:
    vue3早已具备抛弃虚拟DOM的能力了
    Python 无废话-基础知识函数详解
    价格监测,怎样才算全覆盖
    计算机竞赛 推荐系统设计与实现 协同过滤推荐算法
    会场及展位变更通知 | GOPS全球运维大会地址更改,龙智展位更换至#106
    kafka-python中消费参数auto.offset.reset详解
    如何判断去噪之后的变压器局部放电是内部还是外部信号
    如何入门渗透测试(非常详细),从零基础入门到精通,看完这一篇就够了
    计算机网络
    HDLbits: Dualedge
  • 原文地址:https://blog.csdn.net/szullc/article/details/125550379