宏展开在预处理中进行,觉得无非就是替换,很简单。最近遇到一个问题,看了一些关于嵌套宏展开的文章,处处皆学问啊!
这三篇是英文的。
Macro ssession of TheGNU C Preprocessor
中文资料:
The Macro Expansion Process 宏展开过程 (上面英文版本The Macro Expansion Process的翻译)
http://cpp.ezbty.org/myfiles/boost/libs/wave/doc/macro_expansion_process.html
这个很好!
http://learn.akae.cn/media/ch21s02.html#id2798306
将其中一个章节摘录入下:
2.4. 宏展开的步骤
以上举的宏展开的例子都是最简单的,有些宏展开的过程要做多次替换,例如:
#define sh(x) printf("n" #x "=%d, or %d\n",n##x,alt[x])
#define sub_z 26
sh(sub_z)
sh(sub_z)
要用sh(x)
这个宏定义来展开,形参x
对应的实参是sub_z
,替换过程如下:
-
#x
要替换成"sub_z"
。 -
n##x
要替换成nsub_z
。 -
除了带
#
和##
运算符的参数之外,其它参数在替换之前要对实参本身做充分的展开,所以应该先把sub_z
展开成26再替换到alt[x]
中x
的位置。 -
现在展开成了
printf("n" "sub_z" "=%d, or %d\n",nsub_z,alt[26])
,所有参数都替换完了,这时编译器会再扫描一遍,再找出可以展开的宏定义来展开,假设nsub_z
或alt
是变量式宏定义,这时会进一步展开。
再举一个例子:
#define x 3
#define f(a) f(x * (a))
#undef x
#define x 2
#define g f
#define t(a) a
t(t(g)(0) + t)(1);
展开的步骤是:
-
先把
g
展开成f
再替换到#define t(a) a
中,得到t(f(0) + t)(1);
。 -
根据
#define f(a) f(x * (a))
,得到t(f(x * (0)) + t)(1);
。 -
把
x
替换成2,得到t(f(2 * (0)) + t)(1);
。注意,一开始定义x
为3,但是后来用#undef x
取消了x
的定义,又重新定义x
为2。当处理到t(t(g)(0) + t)(1);
这一行代码时x
已经定义成2了,所以用2来替换。还要注意一点,现在得到的t(f(2 * (0)) + t)(1);
中仍然有f
,但不能再次根据#define f(a) f(x * (a))
展开了,f(2 * (0))
就是由展开f(0)
得到的,这里面再遇到f
就不展开了,这样规定可以避免无穷展开(类似于无穷递归),因此我们可以放心地使用递归定义,例如#define a a[0]
,#define a a.member
等。 -
根据
#define t(a) a
,最终展开成f(2 * (0)) + t(1);
。这时不能再展开t(1)
了,因为这里的t
就是由展开t(f(2 * (0)) + t)
得到的,所以不能再展开了。
[总结]
宏展开的几个规律:
1. 宏替换是从左到右处理的。
2. 宏嵌套时,展开顺序是深度展开(跟遍历二叉树的深度遍历意思类似,与广度遍历区分)
3. 遇到“#”, “##” 时,按广度展开,第二遍扫描,再深度展开。
4. 嵌套自己的,只展开一次,第二次保持该宏(可能是要用一个函数定义了)。