C 语言##和#预处理标记的用法及原理分析

发布时间 2023-11-29 09:50:26作者: 北ღ冥

C 语言中"##“和”#"用法解密
一、概述
C语言中指针和宏是很容易让人用错的,特别是指针,即使工作多年的老鸟也很有可能在这上面马失前蹄。当然了宏也不例外,宏中有一些有意思的用法,如果我们没有了解到或者不是非常清楚的话,就很容易犯错或者说是看不懂别人的代码。
下面一起来看一下C语言中的"##“和”#"的用法
二、"##“和”#"的用法及原理分析
很多介绍这两者使用的文章里面都有这样一句话:使用#把宏参数变为一个字符串,用##把两个宏参数贴合在一起。那么具体是怎么实现的呢?他们的功能仅仅是如此吗?接下来让我们结合代码来一探究竟。
我们需要明白的是,#和##都属于预处理标记,即编译器会在预处理阶段进行相关的替换或者处理。
"#"的用法及原理
用法及原理探究
先说结论:

"#"的功能是将其后面的宏参数进行字符串化操作。
宏定义函数的参数与预处理标记 ‘#’ 之间出现的每一个空格都会被删除,并删除第一个预处理标记之前和最后一个预处理标记之后的空白字符,但是宏定义函数参数中的空格会保留。
空参数转化为空,即宏定义函数入参为空,那么展开的时候也为空。
我们先来看一个例子,理解其第一点用法,并且熟悉下这种用法:

 1 #include <stdio.h>
 2 
 3 #define MAKE_STR(s) (#s)
 4 
 5 int main(void)
 6 {
 7     printf("hello");
 8     printf(MAKE_STR(hello));
 9 
10     return 0;
11 }

将其进行编译后,两个printf都会输出:hello

那么它具体是怎么实现的呢?其实很简单就是在其宏变量被替换,在其左右两侧加上双引号。为了探究这个过程,我们在用gcc编译的时候加上-E选项来看下编译器的预处理过程:

可以看到,gcc编译器的预处理过程和我们的猜想是一致的,#预处理标记的作用确实只是在我们指定的参数两旁加上双引号使其成为一个字符串了。这里也可以看出#预处理标记的工作原理了。

可以看到上面的例子中没有换行,我们将其晚上一下。提供两种思路:

在转换的字符串中加\n换行符。
在宏函数中加入"\n"换行
第一种方式,验证如下:

 1 #include <stdio.h>
 2 
 3 #define MAKE_STR(s) (#s)
 4 
 5 int main(void)
 6 {
 7     printf("hello\n");
 8     printf(MAKE_STR(hello\n));
 9 
10     return 0;
11 }

 

输出如下:

可以看到,这种方式需要我们再每个字符后边加上\n,并不智能,那么有没有简单的办法呢?有的,利用C语言会自动将相邻的两个字符串合并的特性,第二种方法如下:

 1 #include <stdio.h>
 2 
 3 #define MAKE_STR(s) (#s"\n")
 4 
 5 int main(void)
 6 {
 7     printf("hello\n");
 8     printf(MAKE_STR(hello));
 9 
10     return 0;
11 }

 

运行结果如下:

显然,第二种方法会显得更加聪明一点儿。

2. "#"用法深入
先来看下上面提到的第二点: 宏定义函数的参数与预处理标记 ‘#’ 之间出现的每一个空格都会被删除,并删除第一个预处理标记之前和最后一个预处理标记之后的空白字符,但是宏定义函数参数中的空格会保留。
先看例子:

 1 #include <stdio.h>
 2 
 3 #define MAKE_STR(s) ("123" # s "456\n")
 4 
 5 int main(void)
 6 {
 7     printf("hello world\n");
 8     printf(MAKE_STR(hello world));
 9 
10     return 0;
11 }            

 

运行结果为:

可以看到,#和宏定义参数s之间的空格被删除掉了。字符串"123"和字符串"456"和# s之间的空格也被删除掉了。
但是宏参数hello world之间的空格并没有被删除。
注意:'#'只能用于传入参数的宏定义中,且必须置于宏定义体中的参数名前。

"##"的用法
"##"被称为预处理拼接标记,宏定义展开的时候,用来将其左右两边两个token连接为一个token。注意这里连接的对象是token就行,不一定是宏的变量。

先看一下其基本用法,下面的例子是给定底数和指数,并打印出相应的指数的值。

 1 #include <stdio.h>
 2 
 3 #define EXP(x,y) (x##e##y)
 4 
 5 int main(void)
 6 {
 7 printf("hello world\n");
 8 printf("%e\n",EXP(1,3));
 9 
10 return 0;
11 }

 

运行结果如下:

注意"##"表示将左边的字符串和右边的字符串连接起来,但是只能黏贴C语言除了关键字以外合法标识符。再来两个我们经常会用用到的例子:

1 #define def_array(__name,__size) {unint8_t array_##__name[__size]}
2 
3 def_array(myarray,64)
4 将其进行宏展开:
5 uint8_t array_myarray[64];
6 /*************************************/
7 #define log_info(__STRING, __VA_ARGS__)    printf(__STRING,##__VA_ARGS__)

 

需要注意的**##只能用在宏定义中**,用在其他地方会报错。如下:

再深入一点,若是我们遇到函数指针和结构体数组结合的情景,是不是就可以更加灵活的选择对我们想要调用的函数进行选择呢?有很多代码里会根据command来选择执行不同的函数,会做一个回调函数表。

又或者,我们用预处理拼接符 ## 批量生成函数或者变量。这样的话,用上"##"是不是就可以让我们的代码更加简洁了呢?

————————————————
版权声明:本文为CSDN博主「浮云流响」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/wit_732/article/details/107901571