C预处理器,递归宏

时间:2011-04-12 21:27:15

标签: macros c-preprocessor

为什么M(0)和N(0)的结果不同?

#define CAT_I(a, b) a ## b
#define CAT(a, b) CAT_I(a, b)

#define M_0 CAT(x, y)
#define M_1 whatever_else
#define M(a) CAT(M_, a)
M(0);       //  expands to CAT(x, y)

#define N_0() CAT(x, y)
#define N_1() whatever_else
#define N(a) CAT(N_, a)()
N(0);       //  expands to xy

3 个答案:

答案 0 :(得分:17)

实际上,这取决于您对语言标准的解释。例如,在mcpp下,一个严格符合语言标准文本的预处理器实现,第二个产生CAT(x, y);以及[从结果中删除了额外的换行符]:

C:\dev>mcpp -W0 stubby.cpp
#line 1 "C:/dev/stubby.cpp"
        CAT(x, y) ;
        CAT(x, y) ;
C:\dev>

C ++语言规范中有a known inconsistency(C规范中存在相同的不一致性,但我不知道C的缺陷列表在哪里)。规范规定最终的CAT(x, y)不应该被宏替换。意图可能是应该进行宏观替换。

引用链接的缺陷报告:

  

早在20世纪80年代,一些WG14人员就明白,“非替代”措辞与制作伪代码的尝试之间存在细微差别。

     

委员会的决定是,没有现实的“野外”计划可以冒险进入这一领域,并且试图减少不确定性并不值得改变实施或计划的一致性状态的风险。


那么,为什么我们会为M(0)获得与使用最常见预处理器实现的N(0)不同的行为?在替换M时,CAT的第二次调用完全由第一次调用CAT产生的令牌组成:

M(0) 
CAT(M_, 0)
CAT_I(M_, 0)
M_0
CAT(x, y)

如果将M_0定义为由CAT(M, 0)替换,则替换将无限递归。预处理器规范明确禁止通过停止宏替换来进行“严格递归”替换,因此CAT(x, y)不会被宏替换。

但是,在N的替换中,CAT的第二次调用仅包含部分的令牌,这些令牌来自第一次调用CAT

N(0)
CAT(N_, 0)       ()
CAT_I(N_, 0)     ()
N_0              ()
CAT(x, y)
CAT_I(x, y)
xy

此处CAT的第二次调用部分来自第一次调用CAT而部分来自其他令牌的令牌,即()来自替换列表{{1} }}。替换不是严格递归的,因此当替换N的第二次调用时,它不会产生无限递归。

答案 1 :(得分:4)

按照顺序:

1)

M(0); //  expands to CAT(x, y) TRUE 
CAT(M_, 0)
CAT_I(M_, 0)
M_0
CAT(x, y)

2)。

N(0); //  expands to xy TRUE
CAT(N_, 0)()
CAT_I(N_, 0)()
N_0()
CAT(x, y)
CAT_I(x, y)
xy

您只需递归替换宏。

关于##预处理器运算符的注释: 使用##预处理器运算符可以将两个参数“粘合”在一起;这允许在预处理的代码中连接两个令牌。

与标准宏扩展不同,传统的宏扩展没有防止递归的规定。如果类似对象的宏在其替换文本中显示为未加引号,则在重新扫描过程中将再次替换它,以此类似于无限制。 GCC检测何时扩展递归宏,发出错误消息,并在违规宏调用后继续。 (gcc online doc

答案 2 :(得分:0)

似乎有些东西你可能没有发现,但你的宏有N(a) CAT(N_,a)(),而M(a)被定义为CAT(M_, a)注意使用的额外参数括号....