2011-04-12 231 views
14

为什么M(0)和N(0)有不同的结果?C预处理器,递归宏

#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 
+0

呃......你到底想要在这里完成什么......为了什么目的? – t0mm13b 2011-04-12 21:30:17

+15

我真的不想做任何事情,只是在处理某些事情时注意到了这一点,而且我很好奇原因。当我不明白某事时,它会让我很难过:)。 – imre 2011-04-12 21:41:32

回答

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开始,部分来自其他标记,即来自N替换列表中的()。替换不是严格递归的,因此当第二次调用CAT被替换时,它不能产生无限递归。

+0

有趣...... VC++中的预处理器和在线Comeau编译器都将N(0)扩展为“xy”。 – imre 2011-04-12 21:44:32

+0

此外,是否有可能解决这个递归限制,并使最后的CAT评估? (除了定义另一个替代CAT?) – imre 2011-04-12 21:52:00

+1

我有一个模糊的记忆,大意是因为提供给'N_0'的'()'来自任何宏扩展之外,这就是一个* new *宏扩展,因此“blue油漆“脱离CAT()',它可以再次扩大。所以这可能是mcpp中的一个错误。 FWIW gcc与Comeau和VC++一致。 – zwol 2011-04-12 21:54:44

0

好像有东西,你可能也没有发现,但宏有N(a) CAT(N_,a)(),而M(一)被定义为CAT(M_, a)注意使用....

+0

我知道。相应地,N_0被定义为函数式(0参数)的宏。由于某种原因,这似乎在递归评估中有所作为,但我不清楚为什么;这是我的问题。 – imre 2011-04-12 21:39:01

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

+1

呃......我还是不明白。这两个序列都达到了相同的CAT(x,y) - 那么为什么在一个案例中停留在那里,而不是另一个呢? – imre 2011-04-12 21:46:56

+0

我认为这里的递归取决于James McNellis所说的标准的解释。尼斯问题imre。 – 2011-04-12 22:08:07

+1

@imre:在M(0)的情况下,第二个CAT(...)调用的结果完全来自第一个CAT(...)调用,因此它是一个严格的递归调用。在'N(0)'的情况下,第二个'CAT(...)'调用仅部分来自第一个'CAT(...)'调用,部分来自其后出现的其他标记('( )'在'N'的替换列表中)。因此,它不是完全递归的。 – 2011-04-12 22:09:16