如何连接两次 C 预处理器并展开宏,如“ 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并分别列出所有的情况,但是有没有一种聪明的方法来做到这一点呢?

82496 次浏览

用途:

#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 预处理器

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

两个层次的间接

在对另一个答案的评论中,凯德鲁 问道解释了为什么这需要两个层次的间接。轻率的回答是因为这是标准所要求的工作方式; 您往往会发现也需要字符串化操作符的等价技巧。

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

在调用类函数宏的参数被标识之后, 参数替换发生。替换列表中的一个参数,除非在前面 通过 ###预处理令牌或后跟 ##预处理令牌(见下文) ,是 在其中包含的所有宏都已被相应的参数替换之后 在被替换之前,每个参数的预处理标记是 完全宏替换,好像他们形成了预处理文件的其余部分; 没有其他 可以使用预处理令牌。

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

EVALUATOR(mine, VARIABLE)

现在发现了宏 EVALUOR,并且将参数隔离为‘ 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 的参数显然是“ mine”,它已经完全展开了。
根据6.10.3.3的规则,我们发现:

mine ## _ ## VARIABLE

##操作员被淘汰后,映射到:

mine_VARIABLE

正如问题中所述。


传统的 C 预处理器

罗伯特 · 吕格尔(Robert Rüger):

传统的 C 预处理器没有令牌粘贴操作符 ##,有什么办法可以解决这个问题吗?

也许是,也许不是,这取决于预处理器。标准预处理器的一个优点是它具有这种工作可靠的设施,而对于标准预处理器有不同的实现。一个要求是,当预处理器替换注释时,它不会像 ANSI 预处理器那样生成空格。GCC (6.3.0) C 预处理器满足这一要求; XCode8.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);

使用 XCode8.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);

海湾合作委员会产生:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"










extern void mine_VARIABLE(char *x);

很接近了,但是不行。当然,YMMV 取决于您正在使用的预标准预处理器。坦白地说,如果你被一个不合作的预处理器所困扰,那么安排一个标准的 C 预处理器来代替标准的预处理器(通常有一种适当配置编译器的方法)可能比花大量时间去找出一种方法来完成这项工作要简单得多。

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

我还没有完全理解 C 标准的每一个字,但我认为这是一个合理的工作模型,可以用来解释 Jonathan Leffler 的回答中显示的解决方案是如何工作的。如果我的理解是错误的,请告诉我,希望用一个最小的例子来打破我的理论。

就我们的目的而言,我们可以认为宏观扩张分为三个步骤:

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

无间接的逐步示例

总机

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


CAT(Y)

然后用以下方式展开:

gcc -E main.c

我们得到:

pref_Y

因为:

步骤1: YCAT的宏参数。

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。所以我们把 a放在 CAT2(x)给出:

CAT2(a)

步骤2: 不需要进行限制

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

步骤3.1: CAT2的参数 x出现在字符串化 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: 使用参数 x = f(a)调用外部 f

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

步骤1.1: 我们根据步骤1、2和3完全展开参数 x = f(1),给出 x = (a + 1)

现在回到第一步,我们把完全展开的 x参数等于 (a + 1),然后把它放到 f的定义中,给出:

((a + 1) + 1)

步骤2和3: 没有发生太多的事情,因为我们没有字符串化,也没有更多的宏可以展开。