如何使用C预处理器连接两次并展开宏,如;arg######MACRO";?
我试图编写一个程序,其中一些函数的名称取决于某个宏变量的值,宏如下:如何使用C预处理器连接两次并展开宏,如;arg######MACRO";?,c,concatenation,token,c-preprocessor,C,Concatenation,Token,C Preprocessor,我试图编写一个程序,其中一些函数的名称取决于某个宏变量的值,宏如下: #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); 因此,这显然是错误的做法。幸运的是,变量的不同可能值的数量很小,因此我可
#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);
因此,这显然是错误的做法。幸运的是,变量的不同可能值的数量很小,因此我可以简单地做一个#if VARIABLE==n
,并分别列出所有情况,但我想知道是否有一种聪明的方法可以做到这一点
#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预处理器
现在宏计算器被发现了,参数被隔离为“我的”和“变量”;然后将后者完全扩展为“3”,并替换为替换字符串:
EVALUATOR(mine, VARIABLE)
PASTER(mine, 3)
其他规则(6.10.3.3“操作员”)涵盖了该操作:
如果在类似宏的函数的替换列表中,参数的前面紧跟着
或者后跟一个##
预处理标记,该参数将替换为相应的
参数的预处理标记序列;[……]
对于类对象宏调用和类函数宏调用,在创建替换列表之前
重新检查是否有更多要替换的宏名称,##
预处理标记的每个实例
在替换列表中(不是从参数中)删除,并执行前面的预处理
令牌与以下预处理令牌连接
因此,替换列表包含x
后接##
,以及##
后接y
;因此,我们:
mine ## _ ## 3
消除##
令牌,并将两侧的令牌连接在一起,将“我的”与“#”和“3”结合起来,从而产生:
mine_3
这是期望的结果
如果我们看一下原始问题,代码是(修改为使用“我的”而不是“某个函数”): 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
):
请注意,在fun、
和VARIABLE
之间没有空格-这很重要,因为如果存在,它会复制到输出中,最终以mine\u3
作为名称,当然这在语法上是无效的。(现在,请把我的头发拿回来好吗?)
使用GCC 6.3.0(运行cpp-traditional x-paste.c
),我得到:
GCC生成:
# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"
extern void mine_VARIABLE(char *x);
#1“x-paste.c”
# 1 ""
# 1 ""
#1“x-paste.c”
外部无效mine_变量(char*x);
接近,但没有骰子。当然,YMMV取决于您使用的预标准预处理器。坦率地说,如果你被一个不合作的预处理器所困扰,那么用一个标准的C预处理器代替预标准预处理器可能会更简单(通常有一种方法可以适当地配置编译器)而不是花太多时间去想办法做这项工作。对
评估者的简明英语解释
两步模式
我还没有完全理解C标准的每一个字,但我认为这是一个合理的工作模型,说明了中显示的解决方案是如何工作的。如果我的理解不正确,请告诉我,希望用一个最小的例子打破我的理论
就我们的目的而言,我们可以认为宏观扩张分为三个步骤:
- 如果它们是连接或字符串化的一部分,则会完全按照宏调用中给定的字符串进行替换,而不会进行扩展
- 否则,它们首先完全展开,然后才被替换
#define CAT(x) pref_ ## x
#define Y a
CAT(Y)
并将其扩展为:
gcc -E main.c
我们得到:
pref_Y
pref_a
因为:
第1步:Y
是CAT
的宏参数
x
出现在字符串化pref###x
中。因此,Y
按原样粘贴,无需扩展:
pref_ ## Y
第2步:发生连接,剩下的是:
pref_Y
步骤3:任何进一步的宏替换都会发生。但是pref_Y
不是任何已知的宏,因此它被单独保留
我们可以通过在pref\Y
中添加一个定义来证实这一理论:
#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf
CAT(Y)
现在的结果是:
asdf
因为在上面的步骤3中,pref_Y
现在被定义为宏,因此展开
逐步间接示例
但是,如果我们使用两步模式:
#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a
CAT(Y)
我们得到:
pref_Y
pref_a
步骤1:CAT
被评估
CAT(x)
定义为CAT
#define CAT(x) pref_ ## x
#define Y a
#define pref_Y asdf
CAT(Y)
asdf
#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a
CAT(Y)
pref_a
CAT2(a)
pref_ ## a
pref_a
#define f(x) (x + 1)
f(f(a))
((a + 1) + 1)
((a + 1) + 1)