首页 文章

宏扩展:与逗号的争论

提问于
浏览
3

我正在处理的代码使用一些非常复杂的宏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 设置为整个 PARAMB 以及 C 一无所获 . 结果就是这样

int a = 1,2,3 +  + ;

虽然MSVC警告 not enough actual parameters for macro 'MACROFUNC_INNER' .

  • 是否有可能让MSVC通过一些技巧进行扩展(另一层宏强制进行第二次扩展,一些放置好##或#,......) . 承认改变构造工作的方式不是一种选择 . (即:我可以自己解决问题吗?)

  • C标准对这种角落案例有什么看法?我无法在C11规范中找到明确告诉如何处理包含参数列表的参数的任何内容 . (即:我可以与代码的作者争论,他必须再次编写它,或者只是MVSC不符合?)

3 回答

  • 0

    MSVC不符合要求 . 该标准实际上是明确的,虽然它没有必要提到这个特殊情况,这并不例外 .

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

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

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

    • §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.311由最外部匹配括号限定的预处理标记序列形成了类函数宏的参数列表 . 列表中的各个参数由逗号预处理标记分隔,但匹配内部括号之间的逗号预处理标记不会分隔参数....§6.10.3.11在识别出类似函数宏的调用参数之后,参数替换发生了 . 在扩展了包含在其中的所有宏之后,替换列表中的参数...被相应的参数替换 . 在被替换之前,每个参数的预处理标记都被完全宏替换,好像它们形成了预处理文件的其余部分...§6.10.3.41替换列表中的所有参数都已被替换... [t]然后重新扫描生成的预处理标记序列,以及源文件的所有后续预处理标记,以替换更多的宏名称 .

  • 5

    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不正确在重新扫描此类宏的扩展之前,将参数扩展为类似函数的宏 . 似乎不太可能有任何简单的解决方法 .

    UPDATE:

    然而,根据@ 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);
    

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

  • 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有条件地编译,作为一种解决方法 .


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

相关问题