了解宏扩展

Understanding macro expansion

最近看到这样的代码:

#define JOIN(lhs, rhs)   JOIN_(lhs, rhs)
#define JOIN_(lhs, rhs)  JOIN__(lhs, rhs)
#define JOIN__(lhs, rhs) lhs##rhs

我测试了代码,像这样调用:

JOIN(Foo, 0);
JOIN_(Foo, 1);
JOIN__(Foo, 2);

JOIN(Foo, JOIN(A,B));
JOIN_(Foo, JOIN(A,B));
JOIN__(Foo, JOIN(A,B));

宏扩展为以下符号:

Foo0
Foo1
Foo2
FooAB
FooAB
FooJOIN

我明白了,它以不同的方式解决争论。调用 JOIN 的任何变体显然与最后一种情况不同。但是这些宏是如何扩展的呢?为什么参数的行为不同?

编辑:Here's 文件

## 标记化运算符不计算(宏扩展)其参数。然而,类似函数的宏扩展 do 评估参数,这就是为什么您会在第一种情况下获得预期(评估)输出的原因。

从技术上讲,宏 JOIN_ 是不必要的,因为 JOIN 中的 lhsrhs 在扩展 JOIN__ 时被评估。这就足够了:

#define JOIN(lhs, rhs)   JOIN__(lhs, rhs)
#define JOIN__(lhs, rhs) lhs##rhs

编辑

3.9.6 Argument Prescan

Macro arguments are completely macro-expanded before they are substituted into a macro body, unless they are stringified or pasted with other tokens. After substitution, the entire macro body, including the substituted arguments, is scanned again for macros to be expanded. The result is that the arguments are scanned twice to expand macro calls in them.

Macros that call other macros that stringify or concatenate. If an argument is stringified or concatenated, the prescan does not occur. If you want to expand a macro, then stringify or concatenate its expansion, you can do that by causing one macro to call another macro that does the stringification or concatenation.

例如,如果您有

#define AFTERX(x) X_ ## x
#define XAFTERX(x) AFTERX(x)
#define TABLESIZE 1024
#define BUFSIZE TABLESIZE

然后 AFTERX(BUFSIZE) 扩展为 X_BUFSIZE,XAFTERX(BUFSIZE) 扩展为 X_1024。 (不是 X_TABLESIZE。Prescan 总是进行完整的扩展。)

CASE1

#define JOIN__(lhs, rhs) lhs##rhs => 因为它有一个标记粘贴运算符,它会连接那些在被替换之前没有完全宏扩展的宏参数。 --> 这是一种糟糕的扩展方式,首先我们不知道,它将传递给它的参数是什么,它不会等待它的扩展,它只会简单地连接它。

因此,当您调用 JOIN__(Foo, JOIN(A,B)); 时,它不会允许 JOIN(A,B) 扩展并将其连接到 FOOJOIN(A,B)。

CASE2 现在,另一方面, #define JOIN_(lhs, rhs) JOIN__(lhs, rhs) => 在这里,没有标记粘贴操作符,宏参数在被代入宏体之前被完全宏展开。因此,它将允许 lhs 和 rhs 扩展并使用扩展参数 JOIN__(FOO,AB) 调用,因此现在 JOIN__ 有一个标记粘贴运算符,它将简单地连接它的参数 FOO 和 AB,即 FOOAB。这是合适的方法。

CASE3 #define JOIN(lhs, rhs) JOIN_(lhs, rhs) => 与 CASE2 相同。

希望,它解释了多层次扩展范式背后的原因。

原创 预处理器运算符 ## 提供了一种在宏扩展期间连接实际参数的方法。如果替换文本中的参数与## 相邻,则该参数被实际参数替换,## 和周围的白色 space 被删除,并重新扫描结果。例如,宏 paste 连接它的两个参数:

#define  paste(front, back)  front ## back

so paste(name, 1) creates the token  name1.