[英]How does the C preprocessor handle circular dependencies?
我想知道C預處理器如何處理循環依賴(#defines)。 這是我的計划:
#define ONE TWO
#define TWO THREE
#define THREE ONE
int main()
{
int ONE, TWO, THREE;
ONE = 1;
TWO = 2;
THREE = 3;
printf ("ONE, TWO, THREE = %d, %d, %d \n",ONE, TWO, THREE);
}
這是預處理器輸出。 我無法弄清楚為什么輸出是這樣的。 我想知道預處理器在這種情況下采取的各種步驟,以提供以下輸出。
# 1 "check_macro.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "check_macro.c"
int main()
{
int ONE, TWO, THREE;
ONE = 1;
TWO = 2;
THREE = 3;
printf ("ONE, TWO, THREE = %d, %d, %d \n",ONE, TWO, THREE);
}
我在linux 3.2.0-49-generic-pae上運行這個程序,並在gcc版本4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)中編譯。
在擴展預處理器宏時,不會擴展該宏的名稱。 所以你的三個符號都被定義為自己:
ONE -> TWO -> THREE -> ONE (not expanded because expansion of ONE is in progress)
TWO -> THREE -> ONE -> TWO ( " TWO " )
THREE -> ONE -> TWO -> THREE ( " THREE " )
這種行為是由C標准的§6.10.3.4(C11草案中的部分編號)設定的,盡管據我所知,該部分的措辭和編號自C89以來沒有變化。 遇到宏名稱時,會將其替換為其定義(並處理#
和##
預處理程序運算符,以及類似函數的宏的參數)。 然后重新掃描結果以獲取更多宏(在文件的其余部分的上下文中):
2 /如果在替換列表的掃描期間找到要替換的宏的名稱(不包括源文件的其余預處理標記),則不會替換它。 此外,如果任何嵌套替換遇到要替換的宏的名稱,則不會替換它...
該條款接着說,任何因遞歸調用而未被替換的令牌都被有效地“凍結”:它永遠不會被替換:
...這些未替換的宏名稱預處理令牌不再可用於進一步替換,即使它們稍后(重新)檢查在其中否則將替換該宏名稱預處理令牌的上下文中。
最后一句所指的情況很少在實踐中出現,但這是我能想到的最簡單的情況:
#define two one,two
#define a(x) b(x)
#define b(x,y) x,y
a(two)
結果是one, two
。 two
被擴展到one,two
更換期間a
,和擴展two
被標記為完全展開。 隨后,擴展b(one,two)
。 這不再是替換two
的上下文,但是b
的第二個參數這two
已被凍結,因此它不再被擴展。
您的問題由出版物ISO / IEC 9899:TC2第6.10.3.4節“重新掃描和進一步更換”,第2段回答,我在此引用為方便起見; 將來, 如果您對規范有疑問 , 請考慮閱讀具體說明 。
如果在替換列表的掃描期間找到要替換的宏的名稱(不包括源文件的其余預處理標記),則不會替換它。 此外,如果任何嵌套替換遇到要替換的宏的名稱,則不會替換它。 這些未替換的宏名稱預處理令牌不再可用於進一步替換,即使它們稍后(重新)檢查在其中否則將替換該宏名稱預處理令牌的上下文中。
https://gcc.gnu.org/onlinedocs/cpp/Self-Referential-Macros.html#Self-Referential-Macros回答有關自引用宏的問題。
答案的關鍵在於,當預處理器找到自引用宏時,它根本不會擴展它們。
我懷疑,相同的邏輯用於防止循環定義的宏的擴展。 否則,預處理器將處於無限擴展狀態。
在您的示例中,您在定義同名變量之前執行宏處理,因此無論宏處理的結果如何,您始終打印1, 2, 3
!
以下是首先定義變量的示例:
#include <stdio.h>
int main()
{
int A = 1, B = 2, C = 3;
#define A B
#define B C
//#define C A
printf("%d\n", A);
printf("%d\n", B);
printf("%d\n", C);
}
這打印3 3 3
。 有點隱蔽,取消注釋#define CA
改變行printf("%d\\n", B);
的行為printf("%d\\n", B);
以下是rici和Eric Lippert的答案中描述的行為的一個很好的演示,即如果在擴展同一個宏時再次遇到宏名稱,則不會重新擴展宏名稱。
test.c
內容:
#define ONE 1, TWO
#define TWO 2, THREE
#define THREE 3, ONE
int foo[] = {
ONE,
TWO,
THREE
};
輸出gcc -E test.c
(不包括初始# 1 ...
行):
int foo[] = {
1, 2, 3, ONE,
2, 3, 1, TWO,
3, 1, 2, THREE
};
(我會將此作為評論發布,但在評論中包含大量代碼塊有點尷尬,所以我將其作為社區Wiki答案。如果您認為將更好地包含在現有答案的一部分中,請隨意復制它並要求我刪除這個CW版本。)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.