宏扩展:使用逗号进行论证

时间:2016-02-04 20:09:26

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

我正在处理的代码使用了一些非常复杂的宏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是一个不会扩展的单个预处理标记,然后设置{{ 1}}对整个APARAM以及B一无所获。结果就是这个

C

虽然MSVC警告int a = 1,2,3 + + ;

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

3 个答案:

答案 0 :(得分:5)

MSVC不符合要求。该标准实际上是明确的,尽管它并不觉得有必要提及这个特殊情况,这并不例外。

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

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

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

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

  4. (以上几乎忽略了字符串化(#)和标记连接(##),这与此问题无关。)

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

    显然(根据@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.311由最外部匹配括号限定的预处理标记序列形成了类函数宏的参数列表。列表中的各个参数由逗号预处理标记分​​隔,但匹配内部括号之间的逗号预处理标记不会分隔参数。...

         

    §6.10.3.11在识别出类似函数宏的调用参数之后,进行参数替换。在扩展了包含在其中的所有宏之后,替换列表中的参数...被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,好像它们形成了预处理文件的其余部分......

         

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

答案 1 :(得分:3)

C11表示每个外观都是类似对象的宏名称

  

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

[6.10.3 / 9]

类似函数的宏它说:

  

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

[6.10.3 / 4]

和此:

  

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

[6.10.3 / 11]

和此:

  

在识别出类似函数的宏的调用参数之后,发生参数替换。在扩展了包含在其中的所有宏之后,替换列表中的参数被相应的参数替换。在被替换之前,每个参数的预处理标记都被完全宏替换,好像它们形成了预处理文件的其余部分;没有其他预处理令牌可供使用。

[6.10.3.1/1]

一般来说,宏也说这个:

  

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

[6.10.3.4/1]

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

<强>更新

然而,根据@ dxiv的答案,可能毕竟有一个解决方案。他在符合标准的行为方面的解决方案的问题是,需要比实际执行的扩展更多的扩展。这很容易提供。他的方法的这种变化适用于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);

我当然把它变得比它需要的更通用了,但是如果你有很多这些可以处理的话,这对你很有帮助。

答案 2 :(得分:0)

足够好,以下似乎在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有条件地编译,作为一种解决方法。

<小时/> [编辑] P.S.正如评论中所指出的,以上是(另一种)非标准的MSVC行为。相反,@rici@JohnBollinger在相应回复中发布的替代解决方法是合规的,因此建议使用。