【问题标题】:C preprocessor macro doesn't parse comma separated tokens?C 预处理器宏不解析逗号分隔的标记?
【发布时间】:2019-11-29 02:15:38
【问题描述】:

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

  • 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() ----> NULL
    • FX("Hello, world%i%s") ----> void

问题是 CHOOSE 返回的,NULL,NULL 被当作单个参数处理!

问题:

  1. 为什么 C 预处理器将 ,NULL,NULL 视为单个参数?
  2. 如何让 C 预处理器将来自 CHOOSE 的结果视为以逗号分隔的参数列表而不是单个参数?

注意:

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

【问题讨论】:

  • 如果逗号作为宏扩展的结果出现,那么这可以是函数调用的参数分隔符,但不能作为另一个宏的参数分隔符,因为预处理器只运行一次。
  • 我认为它会像嵌套宏一样工作!你有什么办法解决这个问题吗?
  • @Rodney 不正确;宏当然可以使用扩展逗号来分隔参数……这是一种常见的 CPP MP 技术;例如,这是一个 pattern matcher demo 利用扩展中逗号的使用。只需了解 CPP 的运作方式即可。

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


【解决方案1】:

在我看来,您将直觉从 C 语言本身带回了 C 预处理器,而这些直觉让您感到厌烦,因为 CPP 的工作方式不同。通常在 C 中,函数将 类型化值 作为参数。表达式不是类型值;他们得到评估以提供这些东西。因此,当您将事物链接起来时,您最终会得到一种由内而外的评估;这塑造了你的直觉。例如,在评估f(g(h(),h()),m()) 时,f 被传递了两个参数,但它不能对g(h(),h()) 做任何事情;必须对其进行评估,结果是一个值,这就是传递给 f 的参数。假设 h 返回 1,m 返回 7,g 返回总和,f 返回乘积。然后 g 对 values 1 和 1 进行评估。 f 对 values 2 和 7 进行评估。大多数 C 编码都使用这种语言,你习惯了这些内部表达式求值,并将结果值传递给函数。但这不是宏的工作方式。

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

像宏这样的函数的宏调用步骤从 (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?)的事实,您正在调用参数数量不足的可变参数宏(无论如何您都不是故意这样做的)。我们开始:

FX()

调用FX,带有一个空标记列表的参数(请注意,对于 CPP,零参数仅在您使用零参数定义宏时才有意义;F() 仅使用空参数调用因为F(,) 用两个空的调用)。那么a.s.发生,这会将 FX 的替换列表从这个...转换为这个:

RM_FIRST (FF(__VA_ARGS__) ())  => RM_FIRST (FF() ())

跳过字符串化/粘贴,因为没有,然后我们执行 r.a.f.r。这将RM_FIRST 识别为宏。 RM_FIRST 有一个参数:FF() ()。所以我们跳到递归级别 2... 调用 RM_FIRST

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

__VA_ARGS__ => 

...IOW,那里什么都没有了。所以我们基本上完成了。

我猜你是 C 函数直觉的;这样做时,您期望 FF() () 进行评估,并将结果作为参数传递给 RM_FIRST,但这不是宏的评估方式。

但是,您可以通过间接方式实现这一点。如果你这样做了:

#define RM_FIRST(...) RM_FIRST_I(__VA_ARGS__)
#define RM_FIRST_I(x,...) __VA_ARGS__

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

RM_FIRST_I(__VA_ARGS__) => RM_FIRST_I( () () ,NULL,NULL ())

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-08-20
    • 2014-04-17
    • 2010-12-18
    • 1970-01-01
    • 2017-08-29
    • 2021-11-01
    • 2021-07-30
    • 2013-07-16
    相关资源
    最近更新 更多