1. 宏展開規(guī)則unsetunset
在C語言預(yù)處理器中,如果宏參數(shù)本身是一個宏,其展開時機遵循一定的規(guī)則。具體來說,宏參數(shù)的展開時機取決于它在宏定義中的位置和使用方式。以下是詳細(xì)的規(guī)則和解釋:
宏參數(shù)的展開時機
當(dāng)宏被調(diào)用時,預(yù)處理器會按照以下步驟處理宏參數(shù):
參數(shù)替換
- 在宏調(diào)用時,預(yù)處理器會將宏參數(shù)替換為實際參數(shù)。
- 如果宏參數(shù)本身是一個宏,預(yù)處理器會先展開這個宏參數(shù),然后再進(jìn)行替換。
替換列表中的宏展開
- 在替換列表中,預(yù)處理器會逐個處理每個符號。如果符號是一個宏,預(yù)處理器會進(jìn)一步展開這個宏。
- 但是,如果符號是一個宏參數(shù),并且這個宏參數(shù)在替換列表中被用作
#
或##
操作符的參數(shù),則不會展開這個宏參數(shù)。
具體規(guī)則
- 參數(shù)展開優(yōu)先于替換列表展開:宏參數(shù)在替換到替換列表之前會被展開。
- 替換列表中的宏展開:替換列表中的符號(包括展開后的參數(shù))會被進(jìn)一步展開,除非它們被用作
#
或##
操作符的參數(shù)。
示例
示例1:參數(shù)展開優(yōu)先
假設(shè)我們有以下宏定義:
#define A 123#define PRINT(x) printf("%d\n", x)
調(diào)用宏:
PRINT(A);
展開過程:
-
參數(shù)替換:
A
是一個宏,預(yù)處理器先展開A
,得到123
。 -
替換列表展開:將
123
替換到PRINT
的替換列表中,得到:printf("%d\n", 123);
最終輸出:
123
示例2:參數(shù)不展開(#
操作符)
假設(shè)我們有以下宏定義:
#define A 123#define STRINGIFY(x) #x
調(diào)用宏:
printf("%s\n", STRINGIFY(A));
展開過程:
-
參數(shù)替換:
A
是一個宏,但因為STRINGIFY
的替換列表中使用了#
操作符,所以不會展開A
。 -
替換列表展開:將
A
替換到STRINGIFY
的替換列表中,得到:printf("%s\n", "A");
最終輸出:
A
示例3:參數(shù)不展開(##
操作符)
假設(shè)我們有以下宏定義:
#define A 123#define CONCAT(x, y) x##y
調(diào)用宏:
CONCAT(A, B);
展開過程:
-
參數(shù)替換:
A
是一個宏,但因為CONCAT
的替換列表中使用了##
操作符,所以不會展開A
。 -
替換列表展開:將
A
和B
替換到CONCAT
的替換列表中,得到:AB
最終結(jié)果:AB
是一個符號,而不是 123B
。
4. 規(guī)則
- 參數(shù)展開優(yōu)先:宏參數(shù)在替換到替換列表之前會被展開。
- 替換列表中的宏展開:替換列表中的符號會被進(jìn)一步展開,除非它們被用作
#
或##
操作符的參數(shù)。 - 特殊情況:如果宏參數(shù)在替換列表中被用作
#
或##
操作符的參數(shù),則不會展開這個宏參數(shù)。
unsetunset2. 宏參數(shù)不展開的處理方法unsetunset
#define STRINGIFY(x) STRINGIFY_HELPER(x)#define STRINGIFY_HELPER(x) #x#define STRINGCAT(x, y) STRINGCAT_HELPER(x, y)#define STRINGCAT_HELPER(x, y) x##y
這些規(guī)則確保了宏的展開過程是可預(yù)測的,同時也提供了一種靈活的方式來控制宏的行為。
這段代碼中定義了兩個宏 STRINGIFY
和 STRINGIFY_HELPER
,以及 STRINGCAT
和 STRINGCAT_HELPER
。之所以需要兩個宏來實現(xiàn)功能,而不是用一個宏直接完成,是因為 C 預(yù)處理器的工作機制。
原因解釋
-
預(yù)處理器的展開規(guī)則在 C 預(yù)處理器中,宏參數(shù)在第一次展開時不會立即被替換為實際值,而是保留其原始形式。只有通過間接展開(即通過另一個宏調(diào)用)才能正確解析參數(shù)的實際值。
-
具體場景分析
- 對于
STRINGIFY(x)
,如果直接定義為#x
,則無法正確處理帶參數(shù)的宏或復(fù)雜表達(dá)式。通過STRINGIFY_HELPER(x)
的間接調(diào)用,可以確保參數(shù)被正確展開后再進(jìn)行字符串化。 - 同理,對于
STRINGCAT(x, y)
,如果直接定義為x##y
,則無法正確連接帶參數(shù)的宏或復(fù)雜表達(dá)式。通過STRINGCAT_HELPER(x, y)
的間接調(diào)用,可以確保參數(shù)被正確展開后再進(jìn)行拼接。 -
示例對比假設(shè)有以下代碼:
#define FOO 123#define STRINGIFY(x) #x#define STRINGCAT(x, y) x##yconst char* str = STRINGIFY(FOO); // 結(jié)果是 "FOO" 而不是 "123"int value = STRINGCAT(FOO, 456); // 結(jié)果是 FOO456 而不是 123456
如果使用間接宏:
#define STRINGIFY(x) STRINGIFY_HELPER(x)#define STRINGIFY_HELPER(x) #x#define STRINGCAT(x, y) STRINGCAT_HELPER(x, y)#define STRINGCAT_HELPER(x, y) x##yconst char* str = STRINGIFY(FOO); // 結(jié)果是 "123"int value = STRINGCAT(FOO, 456); // 結(jié)果是 123456
通過間接宏調(diào)用,參數(shù)會被正確展開,從而實現(xiàn)預(yù)期的功能。
unsetunset總結(jié)unsetunset
定義兩個宏的原因是為了利用 C 預(yù)處理器的兩步展開機制,確保宏參數(shù)能夠被正確解析和處理。如果只用一個宏實現(xiàn),則無法正確處理復(fù)雜的宏參數(shù)或表達(dá)式。