首页 > 解决方案 > 为什么 MSVC 预处理器连接标记的方式与 GCC 和 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 的错误还是我正在编写未定义的行为?

编辑:

感谢答案,问题所在的位置已经确定。MSVC 不符合标准。

然而,最近似乎他们开始认真对待标准并添加了一个新/Zc:preprocessor选项来启用他们的 C/C++ 预处理器的完全符合模式。请参阅: 宣布在 MSVC 中完全支持符合 C/C++ 的预处理器

标签: cvisual-c++c-preprocessor

解决方案


C 2018 6.10.3.1 1 指定宏参数替换:

在确定了调用类函数宏的参数后,将进行参数替换。替换列表中的参数,除非前面有一个###预处理标记或后面有一个##预处理标记(见下文),在其中包含的所有宏都已展开后,将被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理令牌。

CONCAT ( B , CONCAT ( A , A B ) )中,第一个CONCAT宏有参数BCONCAT ( A , A B )。这些参数首先被完全宏替换。

B不是宏,所以它仍然是B.

CONCAT ( A , A B )中,参数AA B完全被宏替换,但它们不是宏,所以它们仍然是AA B

然后CONCAT ( A , A B )替换为NUMBERSIGNS ( A , A B )

然后 6.10.3.4 1 告诉我们:

在替换列表中的所有参数都被替换并#进行##处理之后,所有地标预处理标记都将被删除。然后重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多宏名称。

所以NUMBERSIGNS ( A , A B )A ## A B. 然后将 之前和之后的标记##连接起来,形成AA B(根据 6.10.3.3 3)。

AA B然后根据 6.10.3.4 1 再次重新扫描此序列。由于AA是宏,因此将其替换为没有标记,只留下B. 这样就完成了 first 的第二个参数的展开CONCAT

因此,在参数替换之后,我们有CONCAT ( B , B ).

现在CONCAT被替换,形成NUMBERSIGNS ( B , B )

由于NUMBERSIGNS是宏,因此将其替换为B ## B. 然后将之前和之后的标记##连接起来,形成BB.

这被重新扫描,并且BB被替换为没有标记。

最终结果是没有令牌。GCC是正确的,MSVC的结果不符合C标准。


推荐阅读