标准 C 预处理器
$ cat xx.c
#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 -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"
extern void mine_3(char *x);
$
两级间接
在对另一个答案的评论中,Cade Roux 询问为什么这需要两个间接级别。轻率的答案是因为这就是标准要求它工作的方式。您往往会发现您也需要字符串化运算符的等效技巧。
C99 标准的第 6.10.3 节涵盖了“宏替换”,而 6.10.3.1 节涵盖了“参数替换”。
在确定了调用类函数宏的参数后,将进行参数替换。替换列表中的参数,除非前面有一个#
或##
预处理标记或后面有一个##
预处理标记(见下文),在其中包含的所有宏都已展开后,将被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理令牌。
在调用NAME(mine)
中,参数是“我的”;它完全扩展为“我的”;然后将其替换为替换字符串:
EVALUATOR(mine, VARIABLE)
现在发现了宏 EVALUATOR,参数被隔离为 'mine' 和 'VARIABLE';然后后者完全扩展为'3',并替换为替换字符串:
PASTER(mine, 3)
其他规则(6.10.3.3 '## 运算符')涵盖了此操作:
如果在类函数宏的替换列表中,参数紧跟在##
预处理标记之前或之后,则将参数替换为相应参数的预处理标记序列;[...]
对于类对象和类函数宏调用,在重新检查替换列表以替换更多宏名称之前,##
替换列表(不是来自参数)中的预处理标记的每个实例都被删除,并且前面的预处理标记被连接使用以下预处理令牌。
因此,替换列表包含x
后跟##
和##
后跟y
; 所以我们有:
mine ## _ ## 3
并消除##
标记并连接两侧的标记将'mine'与'_'和'3'结合起来产生:
mine_3
这是期望的结果。
如果我们看一下原始问题,代码是(适应使用'mine'而不是'some_function'):
#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE
NAME(mine)
NAME 的论点显然是“我的”,并且已完全扩展。
按照 6.10.3.3 的规则,我们发现:
mine ## _ ## VARIABLE
其中,当##
运算符被消除时,映射到:
mine_VARIABLE
完全按照问题中的报告。
传统的 C 预处理器
罗伯特·吕格 问道:
没有令牌粘贴操作符的传统 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,
请注意, and之间没有空格VARIABLE
——这很重要,因为如果存在,它会被复制到输出中,并且您最终mine_ 3
会得到名称,当然这在语法上是无效的。(现在,请问我可以把头发恢复原状吗?)
使用 GCC 6.3.0(运行cpp -traditional x-paste.c
),我得到:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "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 生成:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
关闭,但没有骰子。当然,YMMV 取决于您使用的预标准预处理器。坦率地说,如果您遇到不合作的预处理器,那么安排使用标准 C 预处理器代替预标准的预处理器(通常有一种适当配置编译器的方法)可能比花很多时间试图找到一种方法来完成这项工作。