如何使用C预处理器连接两次并展开宏,如;arg######MACRO";?

如何使用C预处理器连接两次并展开宏,如;arg######MACRO";?,c,concatenation,token,c-preprocessor,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); 因此,这显然是错误的做法。幸运的是,变量的不同可能值的数量很小,因此我可

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

#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);
因此,这显然是错误的做法。幸运的是,变量的不同可能值的数量很小,因此我可以简单地做一个
#if VARIABLE==n
,并分别列出所有情况,但我想知道是否有一种聪明的方法可以做到这一点

#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预处理器 现在宏计算器被发现了,参数被隔离为“我的”和“变量”;然后将后者完全扩展为“3”,并替换为替换字符串:

EVALUATOR(mine, VARIABLE)
PASTER(mine, 3)
其他规则(6.10.3.3“操作员”)涵盖了该操作:

如果在类似宏的函数的替换列表中,参数的前面紧跟着 或者后跟一个
##
预处理标记,该参数将替换为相应的 参数的预处理标记序列;[……]

对于类对象宏调用和类函数宏调用,在创建替换列表之前 重新检查是否有更多要替换的宏名称,
##
预处理标记的每个实例 在替换列表中(不是从参数中)删除,并执行前面的预处理 令牌与以下预处理令牌连接

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

mine ## _ ## 3
消除
##
令牌,并将两侧的令牌连接在一起,将“我的”与“#”和“3”结合起来,从而产生:

mine_3
这是期望的结果


如果我们看一下原始问题,代码是(修改为使用“我的”而不是“某个函数”):

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
):

请注意,在
fun、
VARIABLE
之间没有空格-这很重要,因为如果存在,它会复制到输出中,最终以
mine\u3
作为名称,当然这在语法上是无效的。(现在,请把我的头发拿回来好吗?)

使用GCC 6.3.0(运行
cpp-traditional x-paste.c
),我得到:

GCC生成:

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





extern void mine_VARIABLE(char *x);
#1“x-paste.c”
# 1 ""
# 1 ""
#1“x-paste.c”
外部无效mine_变量(char*x);

接近,但没有骰子。当然,YMMV取决于您使用的预标准预处理器。坦率地说,如果你被一个不合作的预处理器所困扰,那么用一个标准的C预处理器代替预标准预处理器可能会更简单(通常有一种方法可以适当地配置编译器)而不是花太多时间去想办法做这项工作。

评估者的简明英语解释
两步模式

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

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

  • (prescan)宏参数将被替换:
    • 如果它们是连接或字符串化的一部分,则会完全按照宏调用中给定的字符串进行替换,而不会进行扩展
    • 否则,它们首先完全展开,然后才被替换
  • 字符串化和连接发生
  • 所有定义的宏都将展开
  • 无需间接操作的逐步示例

    main.c

    #define CAT(x) pref_ ## x
    #define Y a
    
    CAT(Y)
    
    并将其扩展为:

    gcc -E main.c
    
    我们得到:

    pref_Y
    
    pref_a
    
    因为:

    第1步:
    Y
    CAT
    的宏参数

    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_Y
    
    pref_a
    
    步骤1:
    CAT
    被评估

    CAT(x)
    定义为
    CAT
    
    #define CAT(x) pref_ ## x
    #define Y a
    #define pref_Y asdf
    
    CAT(Y)
    
    asdf
    
    #define CAT2(x) pref_ ## x
    #define CAT(x) CAT2(x)
    #define Y a
    
    CAT(Y)
    
    pref_a
    
    CAT2(a)
    
    pref_ ## a
    
    pref_a
    
    #define f(x) (x + 1)
    
    f(f(a))
    
    ((a + 1) + 1)
    
    ((a + 1) + 1)