为什么 MSVC 预处理器连接标记的方式与 GCC 和 Clang 不同?
Why is MSVC preprocessor concatenating tokens differently than GCC and Clang?
最近,我遇到了 MSVC 的问题。这是它的一个最小示例。
#define NUMBERSIGNS(a,b) a##b
#define CONCAT(a,b) NUMBERSIGNS(a,b)
#define AA
#define BB
CONCAT(B, CONCAT(A, A B))
我在想什么:
由于##
前后的参数不会展开,我需要一个NUMBERSIGNS(a,b)
宏来包裹##
,并用CONCAT(a,b)
调用它,所以参数在连接之前得到扩展。
当 CONCAT(B, CONCAT(A, A B))
扩展时,我希望内部 CONCAT(A, A B)
扩展为 AA B
,从而产生 CONCAT(B, AA B)
.
然后我们将AA
展开为
得到CONCAT(B, B)
(我猜MSVC没有做这一步,不知道该不该做)
然后我们有 BB
,它被重新扫描并扩展为
。
经过 gcc 和 clang 的预处理,代码生成空,这是我想要的结果:
而 MSVC 给出:
BAA B
这是 MSVC 的错误还是我编写了未定义的行为?
编辑:
Thanks to the answers, where the problem was has been identified. MSVC did not conform to the Standard.
However, recently it seems they started to take the Standard seriously and added a new /Zc:preprocessor
option to enable a full conforming mode of their C/C++ preprocessor. See:
Announcing full support for a C/C++ conformant preprocessor in MSVC
C 2018 6.10.3.1 1指定宏参数替换:
After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a #
or ##
preprocessing token or followed by a ##
preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.
在CONCAT
(
B
,
CONCAT
(
A
,
A
B
)
)
, 第一个 CONCAT
宏有参数 B
和 CONCAT
(
A
,
A
B
)
。这些参数首先被完全宏替换。
B
不是宏,所以仍然是 B
.
在CONCAT
(
A
,
A
B
)
中,参数A
和A
B
完全被宏替换了,但不是宏,所以还是A
和A
B
.
则CONCAT
(
A
,
A
B
)
替换为NUMBERSIGNS
(
A
,
A
B
)
.
那么6.10.3.4 1告诉我们:
After all parameters in the replacement list have been substituted and #
and ##
processing has taken place, all placemarker preprocessing tokens are removed. The resulting preprocessing token sequence is then rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.
所以NUMBERSIGNS
(
A
,
A
B
)
替换为A
##
A
B
。然后将 ##
前后的标记连接起来,形成 AA
B
(根据 6.10.3.3 3)。
然后根据 6.10.3.4 1 再次重新扫描此序列 AA
B
。由于 AA
是一个宏,因此它被替换为没有标记,只留下 B
.这样就完成了第一个CONCAT
.
的第二个参数的展开
因此,在参数替换之后,我们有 CONCAT
(
B
,
B
)
.
现在CONCAT
被替换,形成NUMBERSIGNS
(
B
,
B
)
.
因为 NUMBERSIGNS
是一个宏,所以它被替换为 B
##
B
。然后将##
前后的token拼接起来,形成BB
.
重新扫描,BB
被替换为无标记。
最后的结果是没有令牌。 GCC正确,MSVC的结果不符合C标准
最近,我遇到了 MSVC 的问题。这是它的一个最小示例。
#define NUMBERSIGNS(a,b) a##b
#define CONCAT(a,b) NUMBERSIGNS(a,b)
#define AA
#define BB
CONCAT(B, CONCAT(A, A B))
我在想什么:
由于##
前后的参数不会展开,我需要一个NUMBERSIGNS(a,b)
宏来包裹##
,并用CONCAT(a,b)
调用它,所以参数在连接之前得到扩展。
当 CONCAT(B, CONCAT(A, A B))
扩展时,我希望内部 CONCAT(A, A B)
扩展为 AA B
,从而产生 CONCAT(B, AA B)
.
然后我们将AA
展开为
得到CONCAT(B, B)
(我猜MSVC没有做这一步,不知道该不该做)
然后我们有 BB
,它被重新扫描并扩展为
。
经过 gcc 和 clang 的预处理,代码生成空,这是我想要的结果:
而 MSVC 给出:
BAA B
这是 MSVC 的错误还是我编写了未定义的行为?
编辑:
Thanks to the answers, where the problem was has been identified. MSVC did not conform to the Standard.
However, recently it seems they started to take the Standard seriously and added a new
/Zc:preprocessor
option to enable a full conforming mode of their C/C++ preprocessor. See: Announcing full support for a C/C++ conformant preprocessor in MSVC
C 2018 6.10.3.1 1指定宏参数替换:
After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a
#
or##
preprocessing token or followed by a##
preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.
在CONCAT
(
B
,
CONCAT
(
A
,
A
B
)
)
, 第一个 CONCAT
宏有参数 B
和 CONCAT
(
A
,
A
B
)
。这些参数首先被完全宏替换。
B
不是宏,所以仍然是 B
.
在CONCAT
(
A
,
A
B
)
中,参数A
和A
B
完全被宏替换了,但不是宏,所以还是A
和A
B
.
则CONCAT
(
A
,
A
B
)
替换为NUMBERSIGNS
(
A
,
A
B
)
.
那么6.10.3.4 1告诉我们:
After all parameters in the replacement list have been substituted and
#
and##
processing has taken place, all placemarker preprocessing tokens are removed. The resulting preprocessing token sequence is then rescanned, along with all subsequent preprocessing tokens of the source file, for more macro names to replace.
所以NUMBERSIGNS
(
A
,
A
B
)
替换为A
##
A
B
。然后将 ##
前后的标记连接起来,形成 AA
B
(根据 6.10.3.3 3)。
然后根据 6.10.3.4 1 再次重新扫描此序列 AA
B
。由于 AA
是一个宏,因此它被替换为没有标记,只留下 B
.这样就完成了第一个CONCAT
.
因此,在参数替换之后,我们有 CONCAT
(
B
,
B
)
.
现在CONCAT
被替换,形成NUMBERSIGNS
(
B
,
B
)
.
因为 NUMBERSIGNS
是一个宏,所以它被替换为 B
##
B
。然后将##
前后的token拼接起来,形成BB
.
重新扫描,BB
被替换为无标记。
最后的结果是没有令牌。 GCC正确,MSVC的结果不符合C标准