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

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

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

显然,这对于便携式代码库是一件坏事。

我的问题:预处理器应该怎么做? 请先评论一下,或先扩展macros?

6 Solutions collect form web for “C预处理器是否首先删除注释或扩展macros?”

不幸的是,最初的ANSI C规范明确地排除了第4节中的任何预处理器特性(“本规范只描述了C语言,它没有规定库或预处理器”)。

不过, C99规范处理了这个问题。 在“预处理”指令parsing之前,“翻译阶段”中的注释被replace为单个空格。 (细节6.10)。

VC ++和GNU C编译器都遵循这种模式 – 其他编译器可能不符合,如果它们是旧的,但如果它符合C99,则应该是安全的。

正如在C99标准中对翻译阶段进行复制粘贴的描述中所描述的,在翻译阶段3中删除注释(它们被单个空白符替代),同时处理预处理指令并且在阶段4中扩展macros。

在C90标准(我只有硬拷贝,所以没有复制粘贴)这两个阶段以相同的顺序发生,尽pipe翻译阶段的描述与C99标准的某些细节略有不同 – 事实在处理预处理指令和扩展macros之前,删除注释并将其replace为单个空白字符。

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

至于应该如何处理“ // ”的注释,C99标准说这(6.4.9 / 2):

除了字符常量,string字符或注释之外,字符//会引入一个注释,该注释包含所有多字节字符,但不包括下一个换行符。

而C ++标准说(2.7):

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

所以你的第一个例子显然是译者的错误 – 当foo()macros被展开时,应保留foo(a)之后的字符 – 注释字符不应该成为the foo()macros的“内容”的一部分。

但是,由于您面对的是一个错误的翻译器,您可能需要将macros定义更改为:

 #define foo(x) /* junk */ 

解决这个错误。

然而(我在这里漂stream的话题…),因为在处理注释之前发生了行拼接(在一个换行符之前的反斜杠),所以你可以碰到类似这样的恶意代码:

 #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 (编译器应该这样做,但是由于trigraphs几乎让所有运行它们的人感到惊讶,一些编译器决定默认closures它们),你可能会也可能不会得到你想要的行为 -当然,无论什么行为。

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

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

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

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

 if (some condition) foo(x); 

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

 #ifdef _TEST_ #define _cerr cerr #else #define _cerr / ## / cerr #endif 
  • 将在一些编译器(VC ++)上工作。 当_TEST_未定义时,

    _cerr …

    将被注释行取代

    // …

我似乎记得,遵守要求三个步骤:

  1. 跳闸
  2. 展开macros
  3. 再次剥离

原因是编译器能够直接接受.i文件。

  • stdout行默认情况下缓冲,无缓冲或不确定?
  • 结构中的匿名联合不在c99中?
  • 状态机教程
  • 整数除法的行为是什么?
  • 主函数的返回值范围
  • C89,C90或C99的所有function都需要原型吗?
  • 在C99中没有“静态”或“外部”有用吗?
  • 为什么在C99之前混合了声明和代码?
  • 为什么C ++ 11不支持指定的初始化列表作为C99?
  • printf宽度说明符保持浮点值的精度
  • uint8_t,uint_fast8_t和uint_least8_t之间的区别