首页 文章

C预处理程序标准行为

提问于
浏览
15

我正在研究预处理器的确切行为的C标准(我需要实现某种C预处理器) . 根据我的理解,我在下面编写的示例(以帮助我的理解)应该是有效的:

#define dds(x) f(x,
#define f(a,b) a+b
dds(eoe)
su)

我期望像宏调用 dds(eoe) 这样的第一个函数被 f(eoe, 替换(注意替换字符串中的逗号),然后在重新扫描输入时将其视为 f(eoe,su) .

但VC 2010测试给了我这个(我告诉VC输出预处理文件):

eoe+et_leoe+et_l
su)

这是违反直觉的,显然是不正确的 . 这是VC 2010的错误还是我对C标准的误解?特别是,像我一样在替换字符串的末尾添加逗号是不正确的吗?我对C标准语法的理解是允许任何 preprocessing-token .

编辑:

我没有GCC或其他版本的VC . 有人可以帮我验证这些编译器 .

3 回答

  • 8

    据我所知,标准的 [cpp.subst/rescan] 部分中没有任何内容可以使您的行为非法,并且clang和gcc正确地将其扩展为 eoe+su ,并且MSC(Visual C)行为必须报告为错误 .

    我没能使它工作,但我设法为你找到一个丑陋的MSC解决方案,使用可变参数 - 你可能会发现它有用,或者你可能没有,但无论如何它是:

    #define f(a,b) (a+b
    #define dds(...) f(__VA_ARGS__)
    

    它扩展为:

    (eoe+
    su)
    

    当然,这不适用于gcc和clang .

  • 2

    我的答案对C预处理器有效,但根据Is a C++ preprocessor identical to a C preprocessor?,差异与此情况无关 .

    来自C,A参考手册,第5版:

    当执行类似函数的宏调用时,在参数处理之后,通过正文的副本替换整个宏调用 . 参数处理如下进行 . 实际参数标记字符串与相应的形式参数名称相关联 . 然后制作正文的副本,其中每个出现的形式参数名称都被与其关联的实际参数标记序列的副本替换 . 然后,该主体副本将替换宏调用 . [...]一旦扩展了宏调用,宏调用的扫描将在扩展开始时恢复,以便可以在扩展中识别宏的名称,以便进一步宏替换 .

    注意扩展中的单词 . 这就是使你的例子无效的原因 . 现在,结合它: UPDATE :阅读下面的评论 .

    [...]宏通过写入其名称,左括号,然后是每个形式参数的实际参数标记序列,然后是右括号来调用 . 实际的参数标记序列用逗号分隔 .

    基本上,这一切都归结为预处理器是否仅在之前的扩展中重新扫描进一步的宏调用,或者它是否会继续读取即使在扩展后出现的令牌 .

    这可能很难想,但我相信你的例子应该发生的事情是在重新扫描期间识别宏名称 f ,并且由于后续的令牌处理显示 f() 的宏调用,你的例子是正确的,应该输出你的期望 . GCC和clang给出了正确的输出,根据这个推理,这也是有效的(并且产量等效输出):

    #define dds f
    #define f(a,b) a+b
    
    dds(eoe,su)
    

    实际上,两个示例中的预处理输出都是相同的 . 至于你用VC获得的输出,我会说你发现了一个bug .

    这符合C99第6.10.3.4节以及C标准第16.3.4节,重新扫描和进一步更换:

    在替换列表中的所有参数都已被替换并且#和##处理已经发生之后,将删除所有地标标记预处理标记 . 然后,重新扫描生成的预处理标记序列以及源文件的所有后续预处理标记,以替换更多的宏名称 .

  • 1

    好吧,我看到的问题是预处理器执行以下操作

    ddx(x)变为f(x,

    但是,f(x,也被定义为(即使它被定义为f(a,b)),所以f(x,扩展为x garbage .

    所以ddx(x)最终转换为x garbage(因为你定义了f(smthing)) .

    你的dds(eoe)实际上扩展为b,其中a是eoe,b是et_l . 无论出于何种原因,它都会这样做两次:) .

    您所做的这种情况是特定于编译器的,取决于预处理器选择如何处理定义扩展 .

相关问题