标准C预处理器
$ cat xx.c
extern void NAME(mine)(char *x);
$ gcc -E xx.c
extern void mine_3(char *x);
$
两级间接寻址
在回答另一个问题的评论中,Cade Roux 问道为什么需要两个级别的间接寻址。简单回答是因为标准要求如此;你通常也会发现在字符串化操作符上也需要等效的技巧。
C99标准的第6.10.3节涵盖了“宏替换”,6.10.3.1涵盖了“参数替换”。
在确认宏类函数调用的实参后,进行参数替换。除非在替换列表中的参数前面有一个#
或##
预处理标记,或者在其后有一个##
预处理标记(见下文),否则将被相应的参数替换。在所有包含其中的宏扩展之后,每个参数的预处理标记都像它们是预处理文件的其余部分一样被完全宏替换;其他预处理标记不可用。
在调用NAME(mine)
中,实参为“mine”;它被完全展开为“mine”;然后被替换到替换字符串中:
EVALUATOR(mine, VARIABLE)
现在发现了宏EVALUATOR,并将参数分离为“mine”和“VARIABLE”;然后将后者完全展开为“3”,并替换到替换字符串中:
PASTER(mine, 3)
该操作受其他规则的覆盖(6.10.3.3“##运算符”):
如果在函数宏的替换列表中,参数紧接着前面或后面有一个##预处理标记,则该参数将被相应参数的预处理标记序列所替换;对于对象宏和函数宏调用,重复检查替换列表之前,在替换列表中(而不是来自参数)的每个##预处理标记实例都将被删除,并将前一个预处理标记连接到后一个预处理标记。因此,替换列表包含x,后面跟着##,还有##后面跟着y;我们有:
mine
消除##
令牌并将两侧的令牌连接起来,将'mine'与'_'和'3'组合起来,得到:
mine_3
这是期望的结果。
如果我们看一下原始问题,代码如下(为了使用“mine”而不是“some_function”进行了调整):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
NAME 的参数明显是'mine',已经完全扩展了。
按照 6.10.3.3 的规则,我们得到:
mine
当省略##
操作符后,其映射为:
mine_VARIABLE
完全按照问题中所报告的。
传统C预处理器
Robert Rüger 问道:
在没有具有标记粘贴运算符##
的传统C预处理器中,有没有任何方法可以做到这一点?
也许有,也许没有——这取决于预处理器。标准预处理器的一个优点是它具有这个可靠工作的设施,而对于预标准预处理器有不同的实现。其中一个要求是当预处理器替换注释时,它不会生成空格,因为ANSI预处理器要求这样做。GCC(6.3.0)C预处理器满足这个要求;XCode 8.2.1的Clang预处理器则不满足。
当它工作时,它完成以下工作(x-paste.c
):
#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
请注意
fun,
和
VARIABLE
之间没有空格——这一点很重要,因为如果有空格,则会将其复制到输出中,你最终得到的名称是
mine_ 3
,这显然是不合法的语法。(现在,请还我我的头发好吗?)使用GCC 6.3.0(运行
cpp -traditional x-paste.c
),我得到了:
extern void mine_3(char *x);
使用 XCode 8.2.1 中的 Clang,我得到:
# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2
extern void mine _ 3(char *x);
这些空格会破坏一切。我注意到两个预处理器都是正确的;不同的预先标准处理器展示了这两种行为,这使得当尝试移植代码时,记号粘贴变得极其麻烦和不可靠。采用带有##
符号的标准大大简化了这一过程。
可能还有其他方法来做到这一点。但是,这种方法行不通:
#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)
extern void NAME(mine)(char *x);
GCC 生成:
extern void mine_VARIABLE(char *x);
接近成功,但还差一点。当然,这取决于您使用的预标准预处理器。如果您被卡在一个不合作的预处理器中,实际上使用标准C预处理器来代替预先标准化的预处理器可能会更简单(通常有一种配置编译器的方法),而不是花费大量时间尝试解决问题。
#define A 0 \n #define M a ## A
中发生了相同的情况,拥有两个##
并不是关键。 - Ciro Santilli OurBigBook.com