1. 宏展开规则unsetunset
在C语言预处理器中,如果宏参数本身是一个宏,其展开时机遵循一定的规则。具体来说,宏参数的展开时机取决于它在宏定义中的位置和使用方式。以下是详细的规则和解释:
宏参数的展开时机
当宏被调用时,预处理器会按照以下步骤处理宏参数:
参数替换
- 在宏调用时,预处理器会将宏参数替换为实际参数。
- 如果宏参数本身是一个宏,预处理器会先展开这个宏参数,然后再进行替换。
替换列表中的宏展开
- 在替换列表中,预处理器会逐个处理每个符号。如果符号是一个宏,预处理器会进一步展开这个宏。
- 但是,如果符号是一个宏参数,并且这个宏参数在替换列表中被用作
#
或##
操作符的参数,则不会展开这个宏参数。
具体规则
- 参数展开优先于替换列表展开:宏参数在替换到替换列表之前会被展开。
- 替换列表中的宏展开:替换列表中的符号(包括展开后的参数)会被进一步展开,除非它们被用作
#
或##
操作符的参数。
示例
示例1:参数展开优先
假设我们有以下宏定义:
#define A 123#define PRINT(x) printf("%d\n", x)
调用宏:
PRINT(A);
展开过程:
-
参数替换:
A
是一个宏,预处理器先展开A
,得到123
。 -
替换列表展开:将
123
替换到PRINT
的替换列表中,得到:printf("%d\n", 123);
最终输出:
123
示例2:参数不展开(#
操作符)
假设我们有以下宏定义:
#define A 123#define STRINGIFY(x) #x
调用宏:
printf("%s\n", STRINGIFY(A));
展开过程:
-
参数替换:
A
是一个宏,但因为STRINGIFY
的替换列表中使用了#
操作符,所以不会展开A
。 -
替换列表展开:将
A
替换到STRINGIFY
的替换列表中,得到:printf("%s\n", "A");
最终输出:
A
示例3:参数不展开(##
操作符)
假设我们有以下宏定义:
#define A 123#define CONCAT(x, y) x##y
调用宏:
CONCAT(A, B);
展开过程:
-
参数替换:
A
是一个宏,但因为CONCAT
的替换列表中使用了##
操作符,所以不会展开A
。 -
替换列表展开:将
A
和B
替换到CONCAT
的替换列表中,得到:AB
最终结果:AB
是一个符号,而不是 123B
。
4. 规则
- 参数展开优先:宏参数在替换到替换列表之前会被展开。
- 替换列表中的宏展开:替换列表中的符号会被进一步展开,除非它们被用作
#
或##
操作符的参数。 - 特殊情况:如果宏参数在替换列表中被用作
#
或##
操作符的参数,则不会展开这个宏参数。
unsetunset2. 宏参数不展开的处理方法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
这些规则确保了宏的展开过程是可预测的,同时也提供了一种灵活的方式来控制宏的行为。
这段代码中定义了两个宏 STRINGIFY
和 STRINGIFY_HELPER
,以及 STRINGCAT
和 STRINGCAT_HELPER
。之所以需要两个宏来实现功能,而不是用一个宏直接完成,是因为 C 预处理器的工作机制。
原因解释
-
预处理器的展开规则在 C 预处理器中,宏参数在第一次展开时不会立即被替换为实际值,而是保留其原始形式。只有通过间接展开(即通过另一个宏调用)才能正确解析参数的实际值。
-
具体场景分析
- 对于
STRINGIFY(x)
,如果直接定义为#x
,则无法正确处理带参数的宏或复杂表达式。通过STRINGIFY_HELPER(x)
的间接调用,可以确保参数被正确展开后再进行字符串化。 - 同理,对于
STRINGCAT(x, y)
,如果直接定义为x##y
,则无法正确连接带参数的宏或复杂表达式。通过STRINGCAT_HELPER(x, y)
的间接调用,可以确保参数被正确展开后再进行拼接。 -
示例对比假设有以下代码:
#define FOO 123#define STRINGIFY(x) #x#define STRINGCAT(x, y) x##yconst char* str = STRINGIFY(FOO); // 结果是 "FOO" 而不是 "123"int value = STRINGCAT(FOO, 456); // 结果是 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); // 结果是 "123"int value = STRINGCAT(FOO, 456); // 结果是 123456
通过间接宏调用,参数会被正确展开,从而实现预期的功能。
unsetunset总结unsetunset
定义两个宏的原因是为了利用 C 预处理器的两步展开机制,确保宏参数能够被正确解析和处理。如果只用一个宏实现,则无法正确处理复杂的宏参数或表达式。