C语言中宏展开规则,你知多少?

1. 宏展开规则unsetunset

在C语言预处理器中,如果宏参数本身是一个宏,其展开时机遵循一定的规则。具体来说,宏参数的展开时机取决于它在宏定义中的位置和使用方式。以下是详细的规则和解释:

宏参数的展开时机

当宏被调用时,预处理器会按照以下步骤处理宏参数:

参数替换

  • 在宏调用时,预处理器会将宏参数替换为实际参数。
  • 如果宏参数本身是一个宏,预处理器会先展开这个宏参数,然后再进行替换。

替换列表中的宏展开

  • 在替换列表中,预处理器会逐个处理每个符号。如果符号是一个宏,预处理器会进一步展开这个宏。
  • 但是,如果符号是一个宏参数,并且这个宏参数在替换列表中被用作###操作符的参数,则不会展开这个宏参数。

具体规则

  • 参数展开优先于替换列表展开:宏参数在替换到替换列表之前会被展开。
  • 替换列表中的宏展开:替换列表中的符号(包括展开后的参数)会被进一步展开,除非它们被用作###操作符的参数。

示例

示例1:参数展开优先

假设我们有以下宏定义:

#define A 123#define PRINT(x) printf("%d\n", x)

调用宏:

PRINT(A);

展开过程:

  1. 参数替换A 是一个宏,预处理器先展开 A,得到 123

  2. 替换列表展开:将 123 替换到 PRINT 的替换列表中,得到:

    printf("%d\n", 123);

最终输出:

123

示例2:参数不展开(#操作符)

假设我们有以下宏定义:

#define A 123#define STRINGIFY(x) #x

调用宏:

printf("%s\n", STRINGIFY(A));

展开过程:

  1. 参数替换A 是一个宏,但因为 STRINGIFY 的替换列表中使用了 # 操作符,所以不会展开 A

  2. 替换列表展开:将 A 替换到 STRINGIFY 的替换列表中,得到:

    printf("%s\n", "A");

最终输出:

A

示例3:参数不展开(##操作符)

假设我们有以下宏定义:

#define A 123#define CONCAT(x, y) x##y

调用宏:

CONCAT(A, B);

展开过程:

  1. 参数替换A 是一个宏,但因为 CONCAT 的替换列表中使用了 ## 操作符,所以不会展开 A

  2. 替换列表展开:将 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 预处理器的工作机制。

原因解释

  1. 预处理器的展开规则在 C 预处理器中,宏参数在第一次展开时不会立即被替换为实际值,而是保留其原始形式。只有通过间接展开(即通过另一个宏调用)才能正确解析参数的实际值。

  2. 具体场景分析

    • 对于 STRINGIFY(x),如果直接定义为 #x,则无法正确处理带参数的宏或复杂表达式。通过 STRINGIFY_HELPER(x) 的间接调用,可以确保参数被正确展开后再进行字符串化。
    • 同理,对于 STRINGCAT(x, y),如果直接定义为 x##y,则无法正确连接带参数的宏或复杂表达式。通过 STRINGCAT_HELPER(x, y) 的间接调用,可以确保参数被正确展开后再进行拼接。
  3. 示例对比假设有以下代码:

    #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 预处理器的两步展开机制,确保宏参数能够被正确解析和处理。如果只用一个宏实现,则无法正确处理复杂的宏参数或表达式。

声明:本内容为作者独立观点,不代表电子星球立场。未经允许不得转载。授权事宜与稿件投诉,请联系:editor@netbroad.com
觉得内容不错的朋友,别忘了一键三连哦!
赞 3
收藏 4
关注 38
成为作者 赚取收益
全部留言
0/200
成为第一个和作者交流的人吧