为什么在C宏中序列迭代起作用?

5
当编写 C 宏时,有一种技巧称为“序列迭代”。其看起来如下所示:
#define CAT(a, ...) PRIMITIVE_CAT(a, __VA_ARGS__)
#define PRIMITIVE_CAT(a, ...) a ## __VA_ARGS__
#define FUNCTION(name) void name();
#define FUNCTION_TABLE(seq) CAT(FUNCTION_TABLE_1 seq, _END)
#define FUNCTION_TABLE_1(x) FUNCTION(x) FUNCTION_TABLE_2
#define FUNCTION_TABLE_2(x) FUNCTION(x) FUNCTION_TABLE_1
#define FUNCTION_TABLE_1_END
#define FUNCTION_TABLE_2_END

FUNCTION_TABLE((x) (y) (z) (e))

SEQUENCE指的是FUCTION_TABLE的参数,参数将逐个处理。但据我所知,在同一范围内,标记不会被扩展两次,因为它们已经被"涂成蓝色"了。当FUNCTION_TABLE_2被扩展时,宏FUNCTION_TABLE_1已经被涂成蓝色。那么为什么还会被扩展呢?

@user694733 抱歉,是我不好。我修改了问题并添加了定义。 - Yyao
2个回答

3

函数样式宏替换是一个递归过程:

  • 首先进行宏的非递归扩展,除了###
  • 然后,递归地扩展任何不是###操作符的标记
  • 对于结果运用###(如果有的话)
  • 重复步骤1到3,直到步骤1-3不再引起任何变化。

这比实现起来要复杂一些!所以针对您的代码:

  • FUNCTION_TABLE((x) (y) (z) (e)) 会展开为 CAT(FUNCTION_TABLE_1 (x) (y) (z) (e), _END)
  • CAT(X, _END) 展开为 PRIMITIVE_CAT(expand(X), _END)。现在来看一下 expand(X) 的展开:
    • FUNCTION_TABLE_1 (x) (y) (z) (e) 展开为 FUNCTION(x) FUNCTION_TABLE_2 (y) (z) (e)
      • FUNCTION(x) 展开为 void x();
      • FUNCTION_TABLE_2 (y) (z) (e) 展开为 FUNCTION(y) FUNCTION_TABLE_1 (z) (e)
      • FUNCTION(y) 展开为 void y();
      • FUNCTION_TABLE_1 (z) (e) 展开为 FUNCTION(z) FUNCTION_TABLE_2(e)
        • FUNCTION(z) 展开为 void z();
        • FUNCTION_TABLE_2(e) 展开为 FUNCTION(e) FUNCTION_TABLE_1
        • FUNCTION(e) 展开为 void e();
        • 结果: void e(); FUNCTION_TABLE_1
        • 结果: void z(); void e(); FUNCTION_TABLE_1
      • 结果: void y(); void z(); void e(); FUNCTION_TABLE_1
      • 结果: void x(); void y(); void z(); void e(); FUNCTION_TABLE_1
  • 因此,在完全展开了X之后,让我们来回顾一下: PRIMITIVE_CAT(void x(); void y(); void z(); void e(); FUNCTION_TABLE_1, _END)
  • 这将展开为 void x(); void y(); void z(); void e(); FUNCTION_TABLE_1_END
  • 重新扫描后,这将变为 void x(); void y(); void z(); void e();

所以如果有三个宏等待展开,整个主体将被扫描三次。在每个宏完全展开后,将进行重新扫描。这是正确的吗? - Yyao
嗯...很难理解这个处理过程。FUNCTION_TABLE_1(x) 被扩展为 void x(); FUNCTION_TABLE_2。此时,FUNCTION_TABLE_2 还不是宏。但是在替换后,FUNCTION_TABLE_2(y) 被扩展为一个宏。看起来处理器重新扫描了替换结果。 - Yyao
我相信我做得没错;这是我自己的解释方式,通过示例:https://dev59.com/f2Yr5IYBdhLWcg3wg6f9#66130832 - Andrew

1
这个想法是,在宏展开中,所有参数的扩展都将以相同的BLUE-SET开始。
在FUNCTION_TABLE(seq)内部,FUNCTION_TABLE_1 seq内部的所有参数的扩展始终从BLUE-SET={seq}开始。因此,在进入FUNCTION_TABLE_1后,BLUE-SET添加了x,但当完成后回到FUNCTION_TABLE的范围时,扩展将再次以BLUE-SET={seq}开始。
因此,第一次展开FUNCTION_TABLE_1(x),并且在此展开中,BLUE-SET={seq,x},但是当FUNCTION_TABLE_1的扩展完成时,它会回到FUNCTION_TABLE,并且将从该范围内展开FUNCTION_TABLE_2(y),再次在FUNCTION_TABLE_2内部展开BLUE-SET={seq,x},等等。

谢谢 :) 但我还有一个问题。在处理中,FUNCTION_TABLE_1(x) 被扩展为 void x(); FUNCTION_TABLE_2。此时,FUNCTION_TABLE_2 还不是宏。但是在替换后,FUNCTION_TABLE_2(y) 被扩展为宏。看起来处理器重新扫描了替换结果。是否真的进行了重新扫描? - Yyao
是的,预处理器在每次评估后生成一个新的流,并始终重新扫描新的流。新的流将具有预处理标记,每个标记都有一个新的BLUE-SET,因此递归不可能。注意:蓝色集合附加在每个预处理标记上,而不是每个流上。 - alinsoar
非常感谢!这对我帮助很大 :) - Yyao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接