为了账号安全,请及时绑定邮箱和手机立即绑定

如何与C预处理器连接两次并展开宏(如“arg#宏”中的宏)?

如何与C预处理器连接两次并展开宏(如“arg#宏”中的宏)?

C C++
慕莱坞森 2019-06-05 14:28:08
如何与C预处理器连接两次并展开宏(如“arg#宏”中的宏)?我正试图编写一个程序,其中某些函数的名称依赖于某个宏变量的值,其宏如下所示:#define VARIABLE 3#define NAME(fun) fun ## _ ## VARIABLEint NAME(some_function)(int a);不幸的是,宏NAME()把它变成int some_function_VARIABLE(int a);而不是int some_function_3(int a);因此,这显然是错误的做法。幸运的是,变量的不同可能值的数目很小,所以我可以简单地做一个#if VARIABLE == n并分别列出所有案例,但我想知道是否有一个聪明的方法来做到这一点。
查看完整描述

2 回答

?
临摹微笑

TA贡献1982条经验 获得超2个赞

标准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);$

两级间接

为什么这需要两个层次的间接。轻率的答案是,因为这是标准要求它工作的方式;你往往会发现,你也需要与字符串操作符一样的技巧。

C99标准第6.10.3节涵盖“宏替换”,6.10.3.1涵盖“参数替换”。

在确定了调用类似于函数的宏的参数之后,就会发生参数替换。替换列表中的参数,除非前面有###预处理令牌或后面是##预处理令牌(见下文),在展开其中包含的所有宏后,将被相应的参数替换。在被替换之前,每个参数的预处理标记都会被宏完全替换,就好像它们构成了预处理文件的其余部分一样;没有其他的预处理标记可用。

在调用中NAME(mine),该参数为“my”;它被完全展开为“my”;然后将其替换为替换字符串:

EVALUATOR(mine, VARIABLE)

现在,宏评估器被发现,参数被隔离为‘my’和‘Variable’;后者随后被完全展开为‘3’,并被替换成替换字符串:

PASTER(mine, 3)

其他规则(6.10.3.3‘#操作符’)涵盖了这方面的操作:

如果在类似函数的宏的替换列表中,参数紧跟在##预处理令牌,将参数替换为相应参数的预处理令牌序列;[.]

对于类似对象的宏调用和类似函数的宏调用,在重新检查替换列表以获得要替换的更多宏名称之前,##替换列表中的预处理令牌(不是从参数中删除)被删除,并将前面的预处理令牌与以下预处理令牌连接起来。

因此,替换列表包含x紧随其后##还有##紧随其后y因此,我们有:

mine ## _ ## 3

和消除##任何一方的令牌和连接令牌将“my”与“_”和“3”组合在一起,从而产生如下结果:

mine_3

这是期望的结果。


如果我们看一下最初的问题,代码(修改为使用‘my’而不是‘SomeFunction’):

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

关于名字的论点显然是“我的”,而且已经完全扩展了。
按照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,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"
 2extern 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预处理器代替预标准处理器(通常有一种适当配置编译器的方法)可能比花很多时间想出一种方法来完成这一工作要简单得多。


查看完整回答
反对 回复 2019-06-05
?
30秒到达战场

TA贡献1828条经验 获得超6个赞

#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);

老实说,你不想知道为什么会这样。如果你知道为什么会起作用,你就会变成那个家伙在工作中,谁知道这类事情,每个人都会来问你问题。=)


编辑:如果你真的想知道它的工作原理,我很乐意发表一个解释,假设没有人比我强。


查看完整回答
反对 回复 2019-06-05
  • 2 回答
  • 0 关注
  • 728 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信