我正在尝试编写一个程序,其中某些函数的名称依赖于某个宏变量的值,其宏如下所示:
#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
并单独列出所有情况,但我想知道是否有一种聪明的方法来做它。
答案 0 :(得分:203)
$ 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 asked为什么这需要两个级别的间接。轻率的回答是因为这就是标准要求它起作用的方式;你倾向于发现你需要与字符串化运算符相同的技巧。
C99标准的第6.10.3节涵盖'宏替换',6.10.3.1涵盖'参数替换'。
在确定了调用类函数宏的参数之后, 参数替换发生。替换列表中的参数,除非在前面 通过
#
或##
预处理令牌或后跟##
预处理令牌(见下文), 在其中包含的所有宏之后用相应的参数替换 扩大。在被替换之前,每个参数的预处理标记都是 完全宏被替换,好像它们形成了预处理文件的其余部分;没有其他 预处理令牌可用。
在调用NAME(mine)
中,参数是'我的';它完全扩展到'我的';然后将其替换为替换字符串:
EVALUATOR(mine, VARIABLE)
现在发现宏EVALUATOR,参数被隔离为'mine'和'VARIABLE';然后将后者完全扩展为'3',并替换为替换字符串:
PASTER(mine, 3)
其他规则(6.10.3.3'## operator')涵盖了此操作:
如果在类似函数的宏的替换列表中,紧跟在前面的参数 或者后跟一个
##
预处理令牌,该参数将被相应的替换 参数的预处理标记序列; [...]对于类似对象和类似函数的宏调用,在替换列表之前 重新检查更多宏名称以替换
##
预处理令牌的每个实例 在替换列表中(不是从参数中)被删除并进行前面的预处理 token与以下预处理标记连接。
因此,替换列表包含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预处理器符合此要求;来自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 :(得分: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);
老实说,你不想知道为什么会这样。如果你知道它为什么会起作用,你就会成为那个知道这种事情的人,每个人都会问你问题。 =)
编辑:如果你真的想知道它为什么会有效,我会高兴地发布一个解释,假设没有人打败我。
答案 2 :(得分:0)
EVALUATOR
两步模式的简单英语解释
我还没有完全理解C标准的每个词,但是我认为这对于https://stackoverflow.com/a/1489985/895245中显示的解决方案的工作方式是一个合理的工作模型,对此进行了更为详细的解释。如果我的理解不正确,请告诉我,希望能提供一个破坏我理论的最小例子。
出于我们的目的,我们可以将宏扩展视为三个步骤:
没有间接说明的分步示例
main.c
#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
由于上面pref_Y
的第3步未定义为宏,因此会扩展。
具有间接性的分步示例
但是,如果我们使用两步模式:
#define CAT2(x) pref_ ## x
#define CAT(x) CAT2(x)
#define Y a
CAT(Y)
我们得到:
pref_a
步骤1:评估CAT
。 x
中CAT
的自变量CAT2(x)
不会出现在字符串化中。因此,Y
在进行替换之前要经过步骤1、2和3的完全扩展,在此省略了它,因为它会小幅扩展到a
。因此,我们将a
放在CAT2(x)
中,给出:
CAT2(a)
第2步:无需进行分类
第3步:展开所有现有的宏。我们有宏CAT2(a)
,因此我们继续对其进行扩展。
步骤3.1:x
的参数CAT2
出现在字符串pref_ ## x
中。因此,按原样粘贴输入字符串a
,得到:
pref_ ## a
步骤3.2:字符串化:
pref_a
第3步:扩展所有其他宏。 pref_a
不是任何宏,所以我们完成了。
GCC参数预扫描文档
海湾合作委员会关于此事的文档也值得一读:https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html
奖励:这些规则如何防止嵌套调用变为无限
现在考虑:
#define f(x) (x + 1)
f(f(a))
扩展为:
((a + 1) + 1)
而不是无限。
让我们分解一下:
步骤1:使用参数f
调用外部x = f(a)
。
在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步:不会发生什么事情,因为我们没有字符串化,也没有更多的宏可以扩展。