C预处理程序宏不解析逗号分隔的标记吗?

时间:2019-07-20 09:26:35

标签: c macros c-preprocessor c99 variadic-macros

我想根据参数数量选择两个函数之一:

  • nargs = 0 ----> f1
  • nargs > 0 ----> f2。

宏执行以下操作:获取第一个参数,然后如果未提供任何参数,它将添加两个逗号“ ,NULL,NULL”。然后它将从返回的参数列表中选择第二个参数。

例如:

  • f("Hello, world%i%s", x , s) ---->“ Hello, world%i%s” ----> void
  • f() ----> ,NULL,NULL ----> NULL

所以我可以根据参数的数量获得null或void。

这是宏:

#define FIRST(a, ...) a
#define COMMA_IF_PARENS(...) ,NULL,NULL
#define IS_EMPTY(...) COMMA_IF_PARENS __VA_ARGS__ ()
#define RM_FIRST(x, ...) __VA_ARGS__
#define CHOOSE(...) IS_EMPTY(FIRST(__VA_ARGS__)) 
#define ff_(...)() CHOOSE (__VA_ARGS__)
#define FF(...)()  ff_(__VA_ARGS__) ()
#define FX(...)  RM_FIRST (FF(__VA_ARGS__) ())
    FF宏的
  • 输出:

    • FF() ----> ,((void*)0),((void*)0);
    • FF("Hello, world%i%s") ----> COMMA_IF_PARENS "Hello, world%i%s" ();
  • FX宏的
  • 输出:

    • FX() ---> void
    • FX("Hello, world%i%s") ----> void
  • 预期的FX输出:

    • FX() ----> NULL
    • FX("Hello, world%i%s") ----> void

问题是从CHOOSE返回的,NULL,NULL被视为单个参数!

问题:

  1. 为什么C预处理程序将,NULL,NULL视为单个参数?
  2. 如何使C预处理器将CHOOSE的结果作为用逗号分隔而不是单个参数的参数列表?

注意:

  • 我想知道为什么C预处理器不能按我期望的那样工作。

1 个答案:

答案 0 :(得分:1)

在我看来,您正在将直觉从C语言本身传递回C预处理程序,而这些直觉正在困扰您,因为CPP的工作方式不同。通常在C语言中,函数将类型化的值作为参数。表达式不是类型化的值;他们得到评估以给予这些东西。因此,当您链接事物时,最终得到的是一种由内而外的评估。这会影响您的直觉。例如,在评估f(g(h(),h()),m())时,向f传递了两个参数,但它对g(h(),h())无效。必须对其求值,结果是一个值,这是传递给f的参数。假设h返回1,m返回7,g返回总和,f返回乘积。然后g评估 1和1。f评估 values 2和7。大多数C编码使用这种语言,并且您习惯了这些内部表达式求值,并将结果值传递给函数。但这不是宏的工作原理。

在宏调用的怪异世界中(谨慎地措辞;我有意忽略条件指令),您的函数不会采用类型化的值;他们采用令牌序列。 CPP确实为您匹配了括号,这意味着F(())是使用参数F调用(),而不是使用参数(后跟{{ 1}}令牌。但是在宏区域中,)用两个参数调用F(G(H(),H()),M())。参数1是令牌序列F;参数2是令牌序列G(H(),H())。我们不评估表达式M()以获得类型化的值,因为没有类型化的值。只有令牌序列。

针对宏之类的函数的宏调用步骤始于(6.10.3.1)参数替换(a.s.)。在A.s.中,CPP首先查看被调用宏的定义,并注意宏的参数在其替换列表中提到的位置。对于未进行串化且未参与粘贴的任何此类提及,CPP都会评估相应的自变量,并且其评估结果将替换替换列表中参数的这些合格提及。接下来,CPP将(6.10.3.2)字符串化,并以不特定的顺序粘贴(6.10.3.3)。完成所有这些操作后,对生成的替换列表(6.10.3.4)进行重新扫描,然后进行进一步替换(r.a.f.r),顾名思义,对其进行重新扫描以进行进一步替换;在此重新扫描过程中,特定的宏被暂时禁用(根据6.10.3.4p2,“涂成蓝色”)。

因此,让我们逐步了解一下;我会忽略这样一个事实,即您使用的是语言扩展(gcc?clang?),其中您在调用可变参数宏且参数数量不足(无论如何,您都没有故意这样做)。我们从以下内容开始:

G

这将调用FX() ,它的单个参数是一个空的令牌列表(请注意,对于CPP,零参数仅在您使用零参数定义宏时才有意义; FX被调用为一个空参数,就像F()被两个空参数调用)。所以然后发生,这将F(,)的替换列表从...转换为:

FX

跳过字符串化/粘贴,因为没有,所以我们做r.a.f.r。这会将RM_FIRST (FF(__VA_ARGS__) ()) => RM_FIRST (FF() ()) 识别为宏。 RM_FIRST有一个参数:RM_FIRST。因此,我们进入了递归级别2 ...调用FF() ()

RM_FIRST本身的调用以a.s开始。假设可变参数部分被视为空白,我们将参数x与RM_FIRST关联,但这就是您的直觉真正失败的地方。替换列表中未提及x,因此FF() ()没有任何反应。那是一个为了你。按照适用于任何FF() ()的扩展名,就好像它是空的一样,我们得到以下信息:

__VA_ARGS__

... IOW,这里什么也没有了。这样我们就完成了。

我猜你是在用C函数实现这种功能;这样做时,您期望__VA_ARGS__ => 进行求值,并将结果作为参数传递给FF() (),但这不是宏求值的方式。

但是,您可以通过间接操作使它发生。如果您改为这样做:

RM_FIRST

...然后我们回到调用#define RM_FIRST(...) RM_FIRST_I(__VA_ARGS__) #define RM_FIRST_I(x,...) __VA_ARGS__ 时,我们有一个不同的故事。在这里,RM_FIRST是可变参数列表的一部分,并且提到了FF() ()。所以在那步骤,我们将得到:

__VA_ARGS__

(只是字面意思……我猜想多余的垃圾是您诊断的一部分;但是我很确定您知道从哪里删除多余的()。)然后,在r.a.f.r.期间,我们看到RM_FIRST_I(__VA_ARGS__) => RM_FIRST_I( () () ,NULL,NULL ()) 被调用,故事就这样了。