如何用C预处理器连接两次并像“ arg ## _ ## MACRO”中那样扩展宏?


152

我正在尝试编写一个程序,其中某些函数的名称取决于具有这样的宏的某个宏变量的值:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

不幸的是,宏NAME()将其变成了

int some_function_VARIABLE(int a);

而不是

int some_function_3(int a);

因此,这显然是错误的解决方法。幸运的是,VARIABLE的不同可能值的数量很小,因此我可以简单地进行an #if VARIABLE == n并单独列出所有情况,但是我想知道是否有一种聪明的方法来做到这一点。


3
您确定不想使用函数指针吗?
捷尔吉Andrasek

8
@Jurily-函数指针在运行时运行,预处理器在(编译前)运行。即使两者都可以用于同一任务,也存在差异。
克里斯·卢兹

1
关键是它使用的是一个快速计算几何库,该库对于特定尺寸是硬连线的。但是,有时有人希望能够以几个不同的维度(例如2和3)使用它,因此人们需要一种简单的方法来生成具有与维度相关的函数和类型名称的代码。而且,代码是用ANSI C编写的,因此带有模板和专门知识的时髦C ++内容不适用于此处。
JJ。

2
投票决定重新开放,因为这个问题专门针对递归宏扩展,而stackoverflow.com/questions/216875/using-in-macros是一个通用的“它有什么用”。这个问题的标题应该更加精确。
西罗Santilli郝海东冠状病六四事件法轮功2015年

我希望这个例子已被最小化:发生同样的事情#define A 0 \n #define M a ## A:拥有两个##不是关键。
西罗Santilli郝海东冠状病六四事件法轮功

Answers:


223

标准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),参数为'mine';它已完全扩展为“我的”;然后将其替换为替换字符串:

EVALUATOR(mine, VARIABLE)

现在发现了宏EVALUATOR,并且参数被隔离为“ mine”和“ VARIABLE”;然后将后者完全扩展为'3',并替换为替换字符串:

PASTER(mine, 3)

其他规则(6.10.3.3'##运算符')涵盖了此操作:

如果在类似函数的宏的替换列表中,参数紧跟在##预处理标记之前或之后,则该参数将被相应参数的预处理标记序列替换;[...]

对于类对象和类函数宏调用,在重新检查替换列表以查找更多要替换的宏名称之前,将##删除替换列表中的预处理令牌的每个实例(而不是从参数中获取),并连接前面的预处理令牌使用以下预处理令牌。

因此,更换列表包含x后跟####随后y; 所以我们有:

mine ## _ ## 3

并消除##令牌并在任一侧串联令牌,将'mine'与'_'和'3'结合起来得到:

mine_3

这是期望的结果。


如果我们看原始问题,代码是(适合使用“我的”而不是“ some_function”):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

NAME的论点显然是“我的”,并且已得到充分扩展。
遵循6.10.3.3的规则,我们发现:

mine ## _ ## VARIABLE

##消除了运算符后,它们映射到:

mine_VARIABLE

完全与问题中所报告的一样。


传统C预处理器

罗伯特·吕格(RobertRü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),我得到:

# 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预处理器来代替标准前者可能会更简单(通常有一种适当配置编译器的方法),而不是花很多时间试图找到一种完成工作的方法。


1
是的,这解决了问题。我知道递归有两个层次的技巧-我必须至少玩一次字符串化-但不知道该怎么做。
JJ。

使用没有令牌粘贴运算符## 的传统C预处理器,可以采取任何措施吗?
罗伯特·鲁格

1
@RobertRüger:它使答案的长度加倍,但是我添加了要覆盖的信息cpp -traditional。请注意,没有明确的答案-这取决于您拥有的预处理器。
乔纳森·莱夫勒

非常感谢您的回答。太好了!同时,我还找到了另一个略有不同的解决方案。看这里。它还有一个问题,尽管它不适用于clang。幸运的是,这对我的应用程序来说不是问题……
RobertRüger16年

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

老实说,您不想知道为什么这样做。如果您知道它为什么起作用,您将成为那个知道这种事情的人,每个人都会来问您问题。=)

编辑:如果您真的想知道它为什么起作用,我会很乐意发表一个解释,假设没有人击败我。


您能否解释一下为什么它需要两个间接级别。我有一个重定向级别的答案,但是我删除了答案,因为我必须将C ++安装到Visual Studio中,然后它才行不通。
Cade Roux
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.