【问题标题】:How can I concatenate twice with the C preprocessor and expand a macro as in "arg ## _ ## MACRO"?如何使用 C 预处理器连接两次并展开宏,如“arg ## _ ## MACRO”?
【发布时间】:2010-12-02 04:11:01
【问题描述】:

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

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

【问题讨论】:

  • 您确定不想使用函数指针吗?
  • @Jurily - 函数指针在运行时工作,预处理器在(之前)编译时工作。有区别,即使两者都可以用于相同的任务。
  • 关键是它使用的是一个快速的计算几何库.. 它是为某个维度硬连线的。但是,有时有人希望能够将它用于几个不同的维度(例如,2 和 3),因此需要一种简单的方法来生成具有与维度相关的函数和类型名称的代码。此外,代码是用 ANSI C 编写的,因此带有模板和专业化的时髦 C++ 东西在这里不适用。
  • 投票重新打开,因为这个问题是关于递归宏扩展的,而stackoverflow.com/questions/216875/using-in-macros 是一个通用的“它有什么好处”。这个问题的标题应该更准确。
  • 我希望这个例子被最小化:#define A 0 \n #define M a ## A 上也会发生同样的情况:有两个 ## 不是关键。

标签: c concatenation token c-preprocessor


【解决方案1】:

用途:

#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++ 安装到我的 Visual Studio 中,然后它就不起作用了。
【解决方案2】:

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

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

Robert Rügerasks:

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

【讨论】:

  • 是的,这解决了问题。我知道两级递归的诀窍——我必须至少玩一次字符串化——但不知道如何做到这一点。
  • 没有标记粘贴运算符##的traditional C preprocessor有什么办法吗?
  • @RobertRüger:答案的长度加倍,但我已添加信息以涵盖cpp -traditional。请注意,没有明确的答案——这取决于您拥有的预处理器。
  • 非常感谢您的回答。这太棒了!与此同时,我还找到了另一个略有不同的解决方案。见here。它也有一个问题,即它不适用于clang。幸运的是,这对我的应用程序来说不是问题......
【解决方案3】:

EVALUATOR两步模式的纯英文解释

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

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

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

无间接的分步示例

main.c

#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 参数预扫描文档

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)

现在回到第 1 步,我们将完全扩展的 x 参数等同于 (a + 1),并将其放入 f 的定义中:

((a + 1) + 1)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2022-01-01
    • 2012-09-20
    • 1970-01-01
    • 2018-01-04
    相关资源
    最近更新 更多