9

gcc 预处理器与 MS VS cl 预处理器之间的另一个区别。考虑以下代码段:

# define A(x) L ## x
# define B A("b")
# define C(x) x
C(A("a" B))

对于“gcc -E”,我们得到以下信息:

L"a" A("b")

对于 'cl /E',输出不同:

L"a" L"b"

MS 预处理器以某种方式执行额外的宏扩展。它的工作算法显然与gcc不同,但这个算法似乎也是一个秘密。有谁知道如何解释观察到的差异以及 MS cl 中的预处理方案是什么?

4

3 回答 3

4

海湾合作委员会是正确的。该标准规定:

C99 6.10.3.4/2(以及 C++98/11 16.3.4/2):如果在替换列表扫描期间找到被替换宏的名称(不包括源文件的其余预处理标记),它不会被替换。

因此,在扩展 时A("a" B),我们首先替换BA("a" A("B"))

A("B")没有被替换,根据引用的规则,所以最终的结果是L"a" A("B").

于 2012-07-13T12:24:03.673 回答
3

迈克的回答是正确的,但他实际上省略了标准的关键部分,该部分说明了为什么会这样:

6.10.3.4/2如果在替换列表的扫描期间找到被替换的宏的名称(不包括源文件的其余预处理标记),则不会替换它。此外,如果任何嵌套替换遇到被替换的宏的名称,它不会被替换。这些未替换的宏名称预处理标记不再可用于进一步替换,即使它们稍后在该宏名称预处理标记将被替换的上下文中进行(重新)检查。

请注意我强调的最后一个条款。

所以 gcc 和 MSVC 都将宏扩展A("a" B)L"a" A("b"),但有趣的情况(MSVC 搞砸了)是宏被宏包裹时C

扩展C宏时,首先检查其参数是否有要扩展的宏A并被扩展。然后将其替换到 C 的主体中,然后再次扫描该主体以查找要替换的宏。现在你可能会认为,因为这是 的扩展C,所以只会跳过名称C,但最后一个子句意味着来自扩展的标记A也将跳过 A的重新扩展。

于 2012-07-13T19:29:54.783 回答
0

基本上有两种方法可以认为应该替换 A 宏的剩余出现:

第一个是处理或宏参数,然后将它们插入到宏替换列表中的相应参数的位置。通常每个参数都被完整的宏替换,就好像它形成了输入文件的其余部分,如标准第 6.10.3.1 节所述。但是,如果参数(此处:x)出现在 ## 旁边,则不会这样做;在这种情况下,参数只是根据 6.10.3.3 替换为参数,没有任何递归宏替换。

第二种方法是第 6.10.3.4 节的“重新扫描和进一步替换”,但对于已经被替换过一次的宏,这不是递归完成的。

所以在这种情况下都不适用,这意味着 gcc 正确地保留了 A 的出现未被替换。

于 2012-07-13T12:51:41.280 回答