[英]How to concatenate twice with the C preprocessor and expand a macro as in "arg ## _ ## MACRO"?
我正在尝试编写一个程序,其中某些函数的名称取决于某个宏变量的值,其宏如下所示:
#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 的不同可能值的数量很少,所以我可以简单地执行#if VARIABLE == n
并分别列出所有情况,但我想知道是否有一种聪明的方法来做到这一点。
$ 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 被发现,参数被隔离为“我的”和“变量”; 后者然后完全扩展为 '3',并替换为替换字符串:
PASTER(mine, 3)
其他规则(6.10.3.3 'The ## operator')涵盖了此操作:
如果在类函数宏的替换列表中,参数紧跟在
##
预处理标记之前或之后,则该参数被相应参数的预处理标记序列替换; [...]对于类对象和类函数的宏调用,在重新检查替换列表以替换更多宏名称之前,替换列表中
##
预处理标记的每个实例(不是来自参数)都将被删除,并删除前面的预处理标记与以下预处理标记连接。
因此,替换列表包含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 预处理器,有什么办法可以解决这个问题吗?
也许,也许不是——这取决于预处理器。 标准预处理器的优点之一是它具有可靠运行的功能,而预标准预处理器有不同的实现。 一个要求是,当预处理器替换注释时,它不会像 ANSI 预处理器那样生成空格。 GCC (6.3.0) C Preprocessor 满足这个要求; 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 预处理器代替预标准预处理器(通常有一种方法可以适当地配置编译器)可能比使用标准 C 预处理器更简单花很多时间试图找出一种方法来完成这项工作。
#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);
老实说,您不想知道为什么会这样。 如果你知道为什么它的工作原理,你就会成为那个家伙在谁知道这样的事情的工作,每个人都会来问你问题。 =)
编辑:如果你真的想知道它为什么有效,我会很乐意发布一个解释,假设没有人能打败我。
EVALUATOR
两步模式的EVALUATOR
英语解释
我还没有完全理解 C 标准的每一个词,但我认为这是一个合理的工作模型,用于说明https://stackoverflow.com/a/1489985/895245 中显示的解决方案如何工作,更详细地解释了一点。 如果我的理解不正确,请告诉我,希望有一个打破我理论的最小例子。
就我们的目的而言,我们可以将宏展开视为分三步进行:
没有间接的分步示例
主文件
#define CAT(x) pref_ ## x
#define Y a
CAT(Y)
并扩展它:
gcc -E main.c
我们得到:
pref_Y
因为:
步骤 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_a
第 1 步:评估CAT
。
CAT(x)
被定义为CAT2(x)
,所以在定义中CAT
参数x
不会出现在字符串化中:字符串化只发生在CAT2
被扩展之后,这在这一步中是看不到的。
因此, Y
在被替换之前完全展开,经历了第 1、2 和 3 步,我们在此省略,因为它简单地展开为a
。 所以我们在CAT2(x)
放入a
:
CAT2(a)
第 2 步:无需进行字符串化
第 3 步:展开所有现有的宏。 我们有宏CAT2(a)
,所以我们继续扩展它。
步骤 3.1: CAT2
的参数x
出现在字符串化pref_ ## x
。 因此, a
原样粘贴输入字符串a
,给出:
pref_ ## a
步骤 3.2:字符串化:
pref_a
第 3 步:扩展任何进一步的宏。 pref_a
不是任何宏,所以我们完成了。
GCC 参数预扫描文档
GCC 关于此事的文档也值得一读: https : //gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html
Bunus:这些规则如何防止嵌套调用无限进行
现在考虑:
#define f(x) (x + 1)
f(f(a))
扩展为:
((a + 1) + 1)
而不是无限。
让我们分解一下:
第 1 步:使用参数x = f(a)
调用外部f
。
在定义f
,参数x
是不是在定义一个级联的一部分(x + 1)
的f
。 因此,它在被替换之前首先完全扩展。
步骤 1.1.:我们根据步骤 1、2 和 3 完全展开参数x = f(1)
,给出x = (a + 1)
。
现在回到第 1 步,我们将完全展开的x
参数等于(a + 1)
,并将其放入f
的定义中,给出:
((a + 1) + 1)
第 2 步和第 3 步:不会发生太多事情,因为我们没有字符串化,也没有更多的宏可以扩展。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.