c - 我怎样才能与 C 预处理器连接两次并扩展一个宏,如 "arg ## _ ## MACRO"?

标签 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);

所以这显然是错误的做法。幸运的是,VARIABLE 的不同可能值的数量很少,所以我可以简单地执行 #if VARIABLE == n 并分别列出所有情况,但是有没有聪明的方法来做到这一点?

最佳答案

标准 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 asked为什么这需要两个级别的间接寻址。轻率的回答是因为这就是标准要求它工作的方式;您往往会发现您也需要字符串化运算符的等效技巧。

C99 标准的第 6.10.3 节介绍了“宏替换”,第 6.10.3.1 节介绍了“参数替换”。

After the arguments for the invocation of a function-like macro have been identified, argument substitution takes place. A parameter in the replacement list, unless preceded by a # or ## preprocessing token or followed by a ## preprocessing token (see below), is replaced by the corresponding argument after all macros contained therein have been expanded. Before being substituted, each argument’s preprocessing tokens are completely macro replaced as if they formed the rest of the preprocessing file; no other preprocessing tokens are available.

在调用NAME(mine)中,参数是'mine';它已完全扩展为“我的”;然后将其替换为替换字符串:

EVALUATOR(mine, VARIABLE)

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

PASTER(mine, 3)

this 的操作包含在其他规则中(6.10.3.3 'The ## operator'):

If, in the replacement list of a function-like macro, a parameter is immediately preceded or followed by a ## preprocessing token, the parameter is replaced by the corresponding argument’s preprocessing token sequence; [...]

For both object-like and function-like macro invocations, before the replacement list is reexamined for more macro names to replace, each instance of a ## preprocessing token in the replacement list (not from an argument) is deleted and the preceding preprocessing token is concatenated with the following preprocessing token.

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

mine ## _ ## 3

并消除 ## 标记并连接两边的标记将“我的”与“_”和“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 预处理器

Robert Rüger asks :

Is there any way do to this with the traditional C preprocessor which does not have the token pasting operator ##?

可能会,也可能不会——这取决于预处理器。标准预处理器的优点之一是它具有可靠工作的功能,而准标准预处理器有不同的实现。一个要求是当预处理器替换注释时,它不会像 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);

那些空间破坏了一切。我注意到两个预处理器都是正确的;不同的准标准预处理器表现出这两种行为,这使得 token 粘贴在尝试移植代码时成为一个极其烦人且不可靠的过程。带有 ## 符号的标准从根本上简化了这一点。

可能还有其他方法可以做到这一点。但是,这不起作用:

#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 - 我怎样才能与 C 预处理器连接两次并扩展一个宏,如 "arg ## _ ## MACRO"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33856840/

相关文章:

c - 以字节数组的形式访问结构体

C:与 NULL 比较

python - 有没有连接 scipy.sparse 矩阵的有效方法?

c++ - 如何在 C++ 中连接两个 LPCWSTR

php - 连接数组的所有值

c++ - 平均分配单词之间的空格C++

linux汇编语言创建目录

c - fork()后谁先执行 : parent or the child?

powershell - 如何将PowerShell输出设置为批处理变量?

linux - Unix 删除最后两个标记