C预处理器是否首先删除注释或扩展宏?

时间:2009-10-02 17:26:59

标签: c comments c-preprocessor c99

考虑这个(可怕的,可怕的,没有好的,非常糟糕的)代码结构:

#define foo(x) // commented out debugging code

// Misformatted to not obscure the point
if (a)
foo(a);
bar(a);

我看过两个编译器的预处理器在这段代码上生成不同的结果:

if (a)
bar(a);

if (a)
;
bar(a);

显然,对于可移植的代码库来说这是件坏事。

我的问题:预处理器应该用这个做什么?首先是Elide注释,还是先扩展宏?

6 个答案:

答案 0 :(得分:30)

不幸的是,原始ANSI C Specification明确排除了第4节中的任何预处理器功能(“此规范仅描述了C语言。它没有为库或预处理器提供任何条件。”)。

C99 specification处理这种明显。在“转换阶段”中,注释将替换为单个空格,该空间在预处理指令解析之前发生。 (详见第6.10节)。

VC++GNU C Compiler都遵循这个范例 - 如果它们年龄较大,其他编译器可能不合规,但如果它符合C99,那么您应该是安全的。

答案 1 :(得分:10)

如C99标准中的this copy-n-pasted decription所述,删除注释(它们被单个空格替换)发生在转换阶段3,同时处理预处理指令并在阶段4中扩展宏。

在C90标准中(我只有硬拷贝,所以没有copy-n-paste)这两个阶段以相同的顺序出现,尽管转换阶段的描述在某些细节上与C99标准略有不同 - 在处理预处理指令并扩展宏之前,注释被删除并替换为单个空白字符这一事实。

同样,C ++标准有两个阶段以相同的顺序发生。

至于如何处理'//'评论,C99标准说明了这一点(6.4.9 / 2):

  

除了字符常量,字符串文字或注释之外,字符//   引入一个注释,其中包括所有多字节字符,但不包括   下一个换行符。

C ++标准说(2.7):

  

字符//开始注释,以下一个换行符结束   字符。

因此,您的第一个示例显然是该翻译器的错误 - ;宏扩展后应保留foo(a)之后的“foo()”字符 - 评论字符不应该是the foo()宏的“内容”的一部分。

但是,由于您遇到了错误的翻译,您可能希望将宏定义更改为:

#define foo(x) /* junk */

解决这个问题。

然而(我在这里偏离主题......),因为在处理注释之前发生了行拼接(在新行之前的反斜杠),你可能遇到类似这些令人讨厌的代码:

#define evil( x) printf( "hello "); // hi there, \
                 printf( "%s\n", x); // you!



int main( int argc, char** argv)
{
    evil( "bastard");

    return 0;
}

任何写这篇文章的人都会感到惊讶。

或者甚至更好,尝试以下,由喜欢盒式评论的人(当然不是我!)写的:

int main( int argc, char** argv)
{
                            //----------------/
    printf( "hello ");      // Hey, what the??/
    printf( "%s\n", "you"); // heck??         /
                            //----------------/
    return 0;
}

取决于你的编译器是否默认处理trigraphs(编译器应该是这样,但由于三元组几乎让所有运行它们的人感到惊讶,一些编译器决定默认关闭它们),你可能会或者可能没有得到你想要的行为 - 无论当然是什么行为。

答案 2 :(得分:5)

根据MSDN,在标记化阶段,注释被替换为单个空格, 这是在扩展宏的预处理阶段之前发生的。

答案 3 :(得分:4)

永远不要在您的宏中添加//注释。如果必须发表评论,请使用/ * * /。另外,你的宏中有一个错误:

#define foo(x) do { } while(0) /* junk */

这样,foo总是可以安全使用。例如:

if (some condition)
    foo(x);
无论是否将foo定义为某个表达式,

都不会抛出编译器错误。

答案 4 :(得分:2)

#ifdef _TEST_
#define _cerr cerr
#else
#define _cerr / ## / cerr
#endif
  • 将适用于某些编译器(VC ++)。如果未定义_TEST_

    _cerr ...

    将被评论专栏

    取代

    // cerr ...

答案 5 :(得分:1)

我似乎记得合规需要三个步骤:

  1. 展开宏
  2. 再次剥离

原因与编译器能够直接接受.i文件有关。