2011-04-12 190 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