我怎样才能用C预处理器连接两次并像“arg ## _ ## MACRO”那样扩展一个宏?

ifsvaxew  于 2023-01-16  发布在  Mac
关注(0)|答案(3)|浏览(136)

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

#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,然后分别列出所有的情况,但是有没有一个聪明的方法来做呢?

cngwdvgl

cngwdvgl1#

标准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节涉及"宏替换",www.example.com涉及"参数替换"。6.10.3.1 covers 'argument substitution'.
在标识了用于调用类似函数的宏的参数之后,将进行参数替换。替换列表中的参数,除非前面有###预处理标记,或者后面有##预处理标记(见下文),在其中包含的所有宏都已展开后,将被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,就像它们构成了预处理文件的其余部分一样;没有其它预处理令牌可用。
在调用NAME(mine)中,参数是"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的参数显然是"mine"的,这是完全扩展的。
根据www.example.com的规则,我们发现:6.10.3.3, we find:

mine ## _ ## VARIABLE

当去掉##运算符时,Map为:

mine_VARIABLE

与问题所述完全相同。

传统C预处理器

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

cgyqldqp

cgyqldqp2#

use:

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

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

raogr8fs

raogr8fs3#

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

我还没有完全理解C标准的每一个字,但我认为这是一个合理的工作模型,说明了乔纳森·莱弗勒的答案中所示的解决方案是如何工作的,解释得更详细一点。如果我的理解不正确,请告诉我,希望用一个最小的例子来打破我的理论。
就我们的目的而言,我们可以认为宏观扩张分三步发生:

  • (预扫描)宏参数被替换:
  • 如果它们是连接或字符串化的一部分,则将完全按照宏调用中给定的字符串进行替换,而不进行扩展
  • 否则,它们首先完全展开,然后才被替换
  • 发生字符串化和连接
  • 所有定义的宏都将展开,包括在字符串化中生成的宏
    • 无间接寻址的分步示例**

main.c

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

CAT(Y)

并将其展开为:

gcc -E main.c

我们得到:

pref_Y

因为:
第一步:YCAT的宏参数。
x出现在字符串化pref_ ## x中。因此,Y将按原样粘贴,而不进行扩展,给出:

pref_ ## Y

第二步:连接发生,我们留下:

pref_Y

第三步:任何进一步的宏替换发生。但是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:不需要进行字符串化
第三步:展开所有现有的宏。我们有宏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)

而不是无限大。
让我们来分析一下:
第一步:用参数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:没有发生什么,因为我们没有字符串化,也没有更多的宏需要扩展。

相关问题