C预处理器宏展开

4

我很难理解在以下情况下C预处理器如何应用重写规则。我有以下宏:

#define _A(x) "A" _##x
#define _B(x) "B" _##x
#define X(x) _##x

这个想法是每个宏使用连接来创建一个新表达式,这个表达式本身可以是一个宏——如果它是一个宏,我希望它会被展开:

现在,以下内容展开的方式正如我所期望的那样:

X(x)       expands to _x
X(A(x))    expands to "A" _x
X(A(B(x))) expands to "A" "B" _x

然而,一旦相同的宏被多次使用,扩展将停止:
X(A(A(x)))       expands to "A" _A(x), expected "A" "A" _x
X(B(B(x)))       expands to "B" _B(x), expected "B" "B" _x
X(A(B(A(x))))    expands to "A" "B" _A(x), expected "A" "B" "A" _x 
X(A(B(A(B(x))))) expands to "A" "B" _A(B(x)), expected "A" "B" "A" "B" _x 

我猜这里有某种“同名宏只能扩展一次”的规则在起作用?是否有什么办法让宏按照我想要的方式扩展?


2
这是因为您使用了##运算符,它不会评估宏;这是Nested macro expansion的重复。 - underscore_d
2
如何评估嵌套的预处理器宏 - underscore_d
也许我的大脑不太正常,但我不明白这怎么成为那些问题的重复了。在你提供的问题中,问题是先扩展宏再进行连接。而我想要做的恰恰相反:先连接,然后再扩展。此外,至少有一些扩展正在发生,否则我发布的示例根本不会扩展(例如,我们将得到X(A(x)) - _A(x),但_A(x)显然被扩展了)。 - MrMobster
1
我认为你在这里的根本问题是宏预处理器在处理时只进行了一次简单的解析和输出。你想要做的实际上是一个不同于##运算符的操作符。听起来你想要一个操作符告诉预处理器使用##运算符生成一个符号,然后循环回去重新评估该符号,看是否有其他需要对这个新符号进行的操作。但这不是预处理器的工作方式。 - Richard Chambers
@RichardChambers但是你如何解释X(A(x))的正确展开呢?使用连接到新宏的间接方法是一种众所周知的技术,并且能够可靠地工作,因为预处理器在第一次展开后会重新扫描。我的问题是,为什么如果我多次使用相同的宏名称,这个过程似乎会停止呢? - MrMobster
1
如果 AB 本身就是宏,而且你没有使用 ## 来构建名称,则会无限递归。这是因为相关的扩展是在参数替换阶段完成的,在将它们放入替换列表之前完全展开所有宏参数。a.s.不受“蓝色涂料”(又名6.10.3.4p2,请参见Toby的答案)的影响;它是一种不同的扫描方式。重新扫描和进一步替换发生在此之后;受到6.10.3.4的影响(因为这就是它所做的),不支持相同宏的递归。 - H Walters
2个回答

5

当我想要处理宏展开时,我通常使用这张图,它是我根据标准中的第6.10.3节构建的。希望它有所帮助...

enter image description here

正如Toby已经提到的,嵌套宏不会递归展开。


谢谢,这非常有用! - MrMobster
...但是不正确。按照是-否-否的决策路径,该块指出:“如果嵌套替换遇到被替换的宏名称,则不会进行替换以避免递归”。这是指蓝色油漆;但是蓝色油漆适用于重新扫描和进一步替换(6.10.3.4);而该块描述的是参数替换(6.10.3.1),没有这样的限制。 - H Walters
哎呀,我得去检查一下!同时先删除答案。 - Jimbo
@H Walters:我承认我的错误,感谢您指出 :) - Jimbo

3
C99草案规定,宏展开中不允许递归:
6.10.3.4 重新扫描和进一步替换
1. 在替换列表中的所有参数都被替换并进行 # 和 ## 处理之后,所有占位符预处理标记将被删除。然后,与源文件的所有后续预处理标记一起重新扫描得到的预处理标记序列,以查找更多要替换的宏名称。
2. 如果在此扫描替换列表时找到正在被替换的宏的名称(不包括源文件的其余预处理标记),则不会进行替换。此外,如果任何嵌套替换遇到正在被替换的宏的名称,则不会进行替换。即使稍后在本应替换该宏名称预处理标记的上下文中再次检查它们,这些未替换的宏名称预处理标记也不再可用于进一步的替换。
因此,X(A(A(x))) 展开为 "A" _A(x),但该展开本身不会再次展开,正如你所看到的。

啊,这正是我担心的... 我想你不可能想出一个巧妙的方法来规避它吧? :) - MrMobster
我不知道有任何方法可以获得你想要的效果 - 抱歉! - Toby Speight
1
@MrMobster 避免预处理器最简单的方法是使用其他预处理器。例如,可以看看 M4 处理器 http://mbreen.com/m4.html - Richard Chambers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接