宏扩展:与逗号的争论

Cil*_*yan 4 c macros gcc visual-c++ 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);
Run Code Online (Sandbox Code Playgroud)

期待的是获得

int a = 1 + 2 + 3;
Run Code Online (Sandbox Code Playgroud)

这适用于最初为GHS编写的编译器以及GCC编写的编译器,但MSVC(2008)认为PARAMS它不会扩展为单个预处理令牌,然后A将其设置为整体PARAM,BC不是任何内容.结果就是这样

int a = 1,2,3 +  + ;
Run Code Online (Sandbox Code Playgroud)

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

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

ric*_*ici 8

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

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

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

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

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

(以上几乎忽略了stringification(#)和token concatenation(##),这与这个问题无关.)

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

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

#define CALL(A,B) A B
#define OUTER(PARAM) CALL(INNER,(PARAM))
#define INNER(A,B,C) whatever
Run Code Online (Sandbox Code Playgroud)

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

§6.10.311由最外部匹配括号限定的预处理标记序列形成类函数宏的参数列表.列表中的各个参数由逗号预处理标记分​​隔,但匹配内部括号之间的逗号预处理标记不会分隔参数....

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

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