171

我正在尝试编写一个程序,其中某些函数的名称取决于某个宏变量的值,其中的宏如下所示:

#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并分别列出所有案例,但是有没有聪明的方法呢?

4

3 回答 3

244

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

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

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

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

EVALUATOR(mine, VARIABLE)

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

PASTER(mine, 3)

其他规则(6.10.3.3 '## 运算符')涵盖了此操作:

如果在类函数宏的替换列表中,参数紧跟在##预处理标记之前或之后,则将参数替换为相应参数的预处理标记序列;[...]

对于类对象和类函数宏调用,在重新检查替换列表以替换更多宏名称之前,##替换列表(不是来自参数)中的预处理标记的每个实例都被删除,并且前面的预处理标记被连接使用以下预处理令牌。

因此,替换列表包含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 预处理器

罗伯特·吕格 问道

没有令牌粘贴操作符的传统 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,请注意, and之间没有空格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 预处理器代替预标准的预处理器(通常有一种适当配置编译器的方法)可能比花很多时间试图找到一种方法来完成这项工作。

于 2009-09-29T00:23:14.140 回答
34

利用:

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

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

于 2009-09-29T00:17:25.853 回答
3

EVALUATOR两步模式的简单英语解释

我还没有完全理解 C 标准的每一个字,但我认为这是一个合理的工作模型,用于Jonathan Leffler 的答案中显示的解决方案如何工作,解释得更详细一点。如果我的理解不正确,请告诉我,希望有一个打破我理论的最小例子。

出于我们的目的,我们可以认为宏扩展分三个步骤进行:

  1. (预扫描)宏参数被替换:
    • 如果它们是连接或字符串化的一部分,它们将被完全替换为宏调用中给出的字符串,而不被扩展
    • 否则,它们首先被完全扩展,然后才被替换
  2. 发生字符串化和连接
  3. 所有定义的宏都被扩展

无间接的分步示例

主程序

#define CAT(x) pref_ ## x
#define Y a

CAT(Y)

并扩展它:

gcc -E main.c

我们得到:

pref_Y

因为:

第 1 步:Y是 的宏参数CAT

x出现在 stringification 中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被评估。

CAT(x)被定义为CAT2(x),因此定义中的参数x不会CAT出现在字符串化中:字符串化只发生在CAT2展开之后,这在此步骤中看不到。

因此,Y在被替换之前完全展开,经过步骤 1、2 和 3,我们在此省略,因为它很容易展开为a. 所以我们投入aCAT2(x)

CAT2(a)

第 2 步:无需进行字符串化

第 3 步:展开所有现有的宏。我们有宏CAT2(a),所以我们继续扩展它。

步骤 3.1:参数x出现CAT2在 stringificationpref_ ## x中。因此,按原样粘贴输入字符串a,给出:

pref_ ## a

步骤 3.2:字符串化:

pref_a

第 3 步:展开任何进一步的宏。pref_a不是任何宏,所以我们完成了。

GCC 参数预扫描文档

GCC 关于此事的文档也值得一读:https ://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html

奖励:这些规则如何防止嵌套调用变得无限

现在考虑:

#define f(x) (x + 1)

f(f(a))

扩展为:

((a + 1) + 1)

而不是无限的。

让我们分解一下:

f第 1 步:使用参数调用外部x = f(a)

在 的定义中f,参数x不是 的定义中串联的(x + 1)一部分f。因此,它在被替换之前首先被完全扩展。

x = f(1)步骤 1.1.:我们根据步骤 1、2 和 3充分展开论证,给出x = (a + 1).

现在回到第 1 步,我们采用完全扩展的x参数 equaling (a + 1),并将其放入给定的定义中f

((a + 1) + 1)

第 2 步和第 3 步:没有发生太多事情,因为我们没有字符串化,也没有要扩展的宏。

于 2020-11-03T11:20:55.857 回答