c - 为什么 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++ 的预处理器
解决方案
C 2018 6.10.3.1 1 指定宏参数替换:
在确定了调用类函数宏的参数后,将进行参数替换。替换列表中的参数,除非前面有一个
#
或##
预处理标记或后面有一个##
预处理标记(见下文),在其中包含的所有宏都已展开后,将被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理令牌。
在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 告诉我们:
在替换列表中的所有参数都被替换并
#
进行##
处理之后,所有地标预处理标记都将被删除。然后重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多宏名称。
所以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标准。
推荐阅读
- python - 如何在 python 中为简单的计算器修复此命令
- react-native - 当我尝试使用 expo-av 查看歌曲的长度时,它给了我一些问题
- r - 循环 cv.glmnet 并获得“最佳”系数
- c++ - 如何将 stl 队列推送函数绑定到 std::function?
- f# - 将异步调用限制为每分钟最多 n 个
- c# - 动态调用 Winforms 表单构造函数 C#
- httpurlconnection - 使用 HttpUrlConnection 获取状态 403,但使用 HttpsUrlConnection 获取状态 200
- php - 如何按类别对自定义分类循环进行排序
- azure-devops - 基本和基本 + 测试计划有什么不同
- typescript - Typescript:使用泛型时如何禁用将 Omit 转换为 Pick 的功能?