【问题标题】:Macro Expansion: Argument with Commas宏扩展:带逗号的参数
【发布时间】:2016-05-14 15:10:57
【问题描述】:

我正在处理的代码使用了一些非常复杂的宏 voodoo 来生成代码,但最终有一个看起来像这样的构造

#define ARGS 1,2,3

#define MACROFUNC_OUTER(PARAMS) MACROFUNC_INNER(PARAMS)
#define MACROFUNC_INNER(A,B,C) A + B + C

int a = MACROFUNC_OUTER(ARGS);

期望得到的是什么

int a = 1 + 2 + 3;

这适用于最初为 (GHS) 和 GCC 编写的编译器,但 MSVC (2008) 将 PARAMS 视为不会扩展的单个预处理令牌,然后将 A 设置为整个PARAMBC 什么都没有。结果是这样的

int a = 1,2,3 +  + ;

而 MSVC 警告 not enough actual parameters for macro 'MACROFUNC_INNER'

  • 是否有可能通过一些技巧让 MSVC 进行扩展(另一层宏强制进行第二次扩展,一些放置得当的 ## 或 #,...)。承认改变构造的工作方式不是一种选择。 (即:我可以自己解决问题吗?
  • C 标准对这种极端情况有什么看法?我在 C11 规范中找不到任何明确说明如何处理包含参数列表的参数的内容。 (即:我可以与代码的作者争论他必须重新编写它,还是只是 MVSC 不符合?

【问题讨论】:

  • MSVC 不符合 C99 或 C11。 MS 从来没有表现出对标准一致性的重视,特别是对语言标准的详细一致性。
  • 我的意见可能跑题了。但是使用在 C 之上发明一种语言的宏是很糟糕的,只有在没有其他方法的情况下才作为最后的手段使用。添加三个值有多难?
  • @WeatherVane 这是一个普遍的问题。一方面,每个人都想要 MCVE。但另一方面,这可能会削弱对作者真实意图的看法,使结构看起来毫无用处。

标签: c visual-c++ gcc macros language-lawyer


【解决方案1】:

MSVC 不符合要求。该标准实际上在这一点上很明确,尽管它认为没有必要提及这种特殊情况,这并不例外。

当遇到类似函数的宏调用时,预处理器:

  1. §6.10.3/11 标识参数,这些参数可能是由非保护逗号分隔的空标记序列 ,(如果逗号在括号 ( kbd>)).

  2. §6.10.3.1/1 对宏主体进行第一次传递,用相应的完全宏扩展参数替换 ### 操作中未使用的每个参数。 (在此步骤中,它不会在宏体中进行其他替换。)

  3. §6.10.3.4/1 重新扫描替换的替换标记序列,根据需要执行更多宏替换。

(以上大多忽略了字符串化(#)和令牌连接(##),与本题无关。)

这种操作顺序明确地导致了编写软件的人所期望的行为。

显然(根据@dxiv,并经过验证here)以下符合标准的解决方法适用于某些版本的 MS Visual Studio:

#define CALL(A,B) A B
#define OUTER(PARAM) CALL(INNER,(PARAM))
#define INNER(A,B,C) whatever

作为参考,来自 C11 标准的实际语言,跳过对 ### 处理的引用:

§6.10.3 11 由最外面的匹配括号界定的预处理标记序列形成了类函数宏的参数列表。列表中的各个参数由逗号预处理标记分​​隔,但匹配内括号之间的逗号预处理标记不分隔参数。...

§6.10.3.1 1 在确定了调用类函数宏的参数后,将进行参数替换。替换列表中的参数…在其中包含的所有宏都被展开后被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,就好像它们形成了预处理文件的其余部分......

§6.10.3.4 1 在替换列表中的所有参数都被替换后……[t]然后重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多宏名称。

【讨论】:

  • 不出所料,试探性的 MSVC 解决方法失败了。
  • @dxiv:这是另一个低概率尝试:#define CALL(X,Y) X Y #define OUTER(PARAM) CALL(INNER, (PARAM))`
  • 不错。这确实有效。您能否评论它是否比我在答案中发布的构造更(或更少)标准?
  • @dxiv:这个(现在编辑为答案)是合规的,应该可以在任何地方工作。我认为约翰·布林格对你的看法是正确的;我的手机没有 gcc,所以我回家后才能测试。
  • 谢谢大家的回答,我明天试试。关于重新扫描,实际上它不是在谈论文本,而是在谈论标记的序列。从这个意义上说,我认为它可以解释为参数仍然只是一个单一的标记,这就是为什么它不会被理解为多个参数的原因。这就是为什么我不确定。但我猜替换列表本身被定义为一个标记列表,所以这将是等价的。
【解决方案2】:

C11 表示每次出现类似对象的宏的名称

[被] 替换为构成指令其余部分的预处理标记的替换列表。然后重新扫描替换列表以获取更多宏名称,如下所示。

[6.10.3/9]

类似函数的宏是这样说的:

如果宏定义中的标识符列表不以省略号结尾,则函数类宏调用中的参数数量 [...] 应等于宏定义中的参数数量。

[6.10.3/4]

还有这个:

由最外面的匹配括号界定的预处理标记序列形成了类函数宏的参数列表。

[6.10.3/11]

还有这个:

在确定了调用类函数宏的参数后,将进行参数替换。替换列表 [...] 中的参数在其中包含的所有宏都已展开后被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,就好像它们形成了预处理文件的其余部分一样;没有其他可用的预处理令牌。

[6.10.3.1/1]

在一般宏中,它也这样说:

在替换列表中的所有参数都被替换后 [... t] 然后重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多宏名称。

[6.10.3.4/1]

在重新扫描此类宏的扩展之前,MSVC++ 没有正确地将参数扩展为类似函数的宏。似乎不太可能有任何简单的解决方法。

更新:

然而,鉴于@dxiv 的回答,可能毕竟有解决方案。他关于符合标准的行为的解决方案的问题在于,需要比实际执行的扩展多一次。这可以很容易地提供。他的方法的这种变体适用于 GCC,因为它应该适用于 GCC,因为它基于 dxiv 声称适用于 MSVC++ 的代码,它似乎也可能适用于那里:

#define EXPAND(x) x
#define PAREN(...) (__VA_ARGS__)
#define EXPAND_F(m, ...) EXPAND(m PAREN(__VA_ARGS__))
#define SUM3(a,b,c) a + b + c
#define ARGS 1,2,3

int sum = EXPAND_F(SUM3, ARGS);

我当然让它比它需要的更通用一点,但如果你有很多这些要处理的话,这可能会对你很有帮助..

【讨论】:

    【解决方案3】:

    奇怪的是,以下似乎在 MSVC 中工作(在 2010 年和 2015 年测试)。

    #define ARGS 1,2,3
    
    #define OUTER(...) INNER PARAN(__VA_ARGS__)
    #define PARAN(...) (__VA_ARGS__)
    #define INNER(A,B,C) A + B + C
    
    int a = OUTER(ARGS);
    

    我不知道它应该按照标准的字母工作,事实上我有预感它不是。作为一种解决方法,仍然可以为 MSVC 有条件地编译。


    [编辑]正如 cmets 中所指出的,以上是(另一种)非标准 MSVC 行为。相反,@rici@JohnBollinger 在各自回复中发布的替代解决方法是合规的,因此建议使用。

    【讨论】:

    • 它在 GCC 4 中不起作用,我认为它不应该。当OUTER(ARGS) 展开时,它应该产生INNER PARAN(1,2,3)。当重新扫描时,标记INNER 不会紧跟左括号,因此它不会被识别为类似函数的宏。 PARAN(1,2,3) 扩展为 (1,2,3),但为时已晚。
    • @JohnBollinger the token INNER is not immediately followed by an open parenthesis。这是我不清楚的一点。我找不到“followed immediately”的要求。快速搜索 [6.10.3/10] 将类函数宏的调用定义为“类函数宏名称的后续实例,后跟一个 ( 作为 下一个预处理标记”,这似乎允许宏名称和括号之间有空格。
    • 问题不是空格或没有空格。就是在重新扫描OUTER()的扩展时,INNER之后的下一个token是(应该是)PARAN就是为什么它不被识别为函数类宏的调用。
    • @dxiv 空格很好。但是只有一次重新扫描,所以PARAN的替换不会触发INNER的扩展
    • 这就是它变得模糊的地方。我的替代阅读:当 OUTER 展开时,INNER not 后跟(,因此它被视为常规文本标记。然后 PARAN 按照函数宏规则展开,所以此时展开为INNER (1,2,3)。最后,重新扫描,此时INNER 被识别为函数宏并展开,得到1+2+3
    猜你喜欢
    • 2021-05-16
    • 2017-03-01
    • 2017-06-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-25
    相关资源
    最近更新 更多