C语言中的cbp文件是什么,深入理解C语言中宏定义

从本质上看,C语言中的宏定义实现的是一个文本替换的功能,似乎很简单的样子,然而这几天去看了下Linux Kernel源码中的各种宏定义,才发现一个宏定义竟然也可以有如此多的奇技淫巧……于是花了一天时间仔细研究了下宏的相关知识,此处整理总结下。

关于宏,网上有一组写得极好的文章,基本上看完这几篇文章就可以对宏有一个深入的理解了:

作者的知乎上也有一份相同的备份。

相同的内容此处就不再重复了,此处列出一些要点。

带参数的宏中可以使用两个特殊运算符,#(Stringification Operator)和##(Token Pasting Operator),作用分别是把宏参数变为字符串字面量和连接两个token。且遇到这两个运算符时,宏参数不会展开。

宏的嵌套展开过程中,已展开过的宏不会重复展开。

宏展开后,会进一步检查是否构成了新的宏,若构成了会进一步展开。

宏定义中也可以使用...代表可变参数,用__VA_ARGS__获取可变参数列表。

宏参数会先展开,之后再进行替换,这也被称为”prescan”.

宏基本上是图灵完备的,所以可以只靠宏实现各种东西……

宏的展开过程遵循以下流程图:

这个流程图是我根据自己的理解和实验画出来的,并不确定完全正确……图中的“已展开宏记录”就是文章中说的“蓝色列表”。

使用gcc编译时,可以通过附加-E参数,让gcc只进行预处理,这样就可以看到各种宏实际展开出来的结果是什么了,如gcc -E -o test.i test.c命令对test.c文件进行预处理,生成test.i文件。

关于宏的展开流程,有一些不太明确的地方,此处用例子说明下,结果都经过gcc预处理验证过。

1

2

3

4

5

6

7

8

9

10#define P 2

#define M K(P)

#define K(a) a a ## a a

#define PP 11

#define FOO(a) #a a

FOO(M)--> FOO(K(P))

--> FOO(2 PP 2)

--> FOO(2 11 2)

--> "M" 2 11 2

遇到#或##时,其相连的宏参数不会展开,然而这不意味着这个宏参数本身不会展开,其他部分用到这个宏参数的地方还是会展开的。

另外,#运算符后必须是一个宏参数,不能是其他东西,不过##两端则无这一要求,来看个例子:

1

2

3

4

5

6

7

8

9

10#define P(a) #a a

#define PP(a) #a a

#define CAT(a) P ## P(P##a(a))

CAT(P)--> PP(PP(P))

--> PP("P" P)

--> "PP(P)" "P" P

CAT(A)--> PP(PA(A))

--> "PA(A)" PA(A)

可以看到,##两端可以是任意token,其作用就是把这两个token合成一个。另外,还可以看到,##是在最开始就进行处理的,所以P(a)这个宏是没有用到的。另外,由##操作符组合产生的新宏是会继续展开的,并不像某些文章说的那样会停止展开。

关于多次扫描展开的问题,有些文章中说的是展开完成后会重新扫描一遍当前字符串,若有可以继续展开的则继续展开,然而实际测试下来并不是这样的。还是来看个例子:

1

2

3

4

5

6

7

8

9

10

11#define BRACKET ()

#define CREATE(a, b) a ## b

#define FOO() 123

CREATE(F, OO) BRACKET --> FOO BRACKET

--> FOO ()

CREATE(F, OO) () --> FOO ()

--> 123

FOO BRACKET --> FOO ()

可以看到,第1、3个例子中,展开到FOO () 之后就没有继续展开下去了,这说明并没有重新扫描字符串这一步,已经处理过的部分不会再次处理的。而第2个例子则说明的确是会再展开合并出新的宏来的,故上面的流程图中使用了”向后扫描一个token,形成一个新的字符串”这样的说法。考虑到token是以空白为界划分的,后面组合出来的新宏只可能是function-like的宏,所以这样的展开方式是不存在歧义的,不会出现原来的宏被组合成其他宏的情况。

如果要继续展开上面未能展开的那两个宏,可以再封装一层:

1

2

3

4

5

6

7

8#define BRACKET ()

#define CREATE(a, b) a ## b

#define FOO() 123

#define EXPAND(...) __VA_ARGS__

EXPAND(CREATE(F, OO) BRACKET)--> EXPAND(FOO())

--> EXPAND(123)

--> 123

这里利用的原理是:宏参数会先尽可能展开后再进行替换。

将宏定义的各种奇技淫巧应用得巅峰造极、神鬼莫测之作就是The Boost Preprocessing library,这是Boost库的一部分,不过和其他部分完全独立,这部分包含了各种数据结构和算法等,而且只有头文件,全部都是宏定义……简直可谓是丧心病狂……Github上有人将这部分独立的代码提取出来了,有兴趣的读者可以去进一步揣摩瞻仰:boost-preprocessor

你可能感兴趣的:(C语言中的cbp文件是什么)