60

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

#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);

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

我的问题:预处理器应该对此做什么?先删除注释,还是先展开宏?

4

6 回答 6

36

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

不过,C99 规范明确地处理了这个问题。在“翻译阶段”中,注释被替换为单个空格,这发生在预处理指令解析之前。(详见第 6.10 节)。

VC++GNU C 编译器都遵循这种范式 - 如果其他编译器较旧,它们可能不兼容,但如果它兼容 C99,你应该是安全的。

于 2009-10-02T17:39:18.117 回答
11

如C99 标准中翻译阶段的复制粘贴描述中所述,删除注释(它们被单个空格替换)发生在翻译阶段 3,而预处理指令被处理,宏在阶段 4 被扩展。

在 C90 标准中(我只有硬拷贝,所以没有复制粘贴)这两个阶段以相同的顺序发生,尽管翻译阶段的描述在某些细节上与 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;
}

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

于 2009-10-02T19:32:13.250 回答
5

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

于 2009-10-02T17:35:58.293 回答
4

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

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

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

if (some condition)
    foo(x);

无论 foo 是否定义为某个表达式,都不会抛出编译器错误。

于 2009-10-02T19:52:53.663 回答
2
#ifdef _TEST_
#define _cerr cerr
#else
#define _cerr / ## / cerr
#endif
  • 将适用于某些编译器(VC++)。_TEST_未定义时,

    _cerr ...

    将被注释行替换

    // 错误...

于 2011-01-06T01:13:55.580 回答
1

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

  1. 扩展宏
  2. 再次剥离

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

于 2009-10-02T20:23:38.920 回答