【问题标题】:Does the C preprocessor strip comments or expand macros first? [duplicate]C 预处理器是否首先剥离注释或扩展宏? [复制]
【发布时间】:2010-12-03 09:50:40
【问题描述】:

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

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

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

我的问题:预处理器应该对此做什么?先去掉cmets,还是先展开宏?

【问题讨论】:

  • 好问题 - 让我努力追踪实际的真实信息 :)
  • 仅供参考,使用“#define foo(x) ##”来做一个更安全的空白宏....(或者是###?:/)
  • 顺便说一句-您使用的编译器在您的第一个示例中表现如何?我很确定它会破坏很多代码——即使在#define 中只使用/* */ cmets 可能很聪明,我的印象是我已经看到了很多'//' cmets 使用。
  • 会不会是预处理器不理解// cmets,但编译器可以?请记住,最初,C 应该只理解 /* */ cmets,而 // 是 C++ 扩展。我认为 C 只用 C99 拾取了//。 (我的历史记录在这里正确吗?)。事实上,无论你使用什么编译器,我都很想知道它是如何处理 /*

标签: c comments c-preprocessor c99


【解决方案1】:

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

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

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

【讨论】:

  • 抱歉,但您链接到的是不是 ANSI C 规范;实际规范在第 2.1.1.2 节中描述了翻译阶段;我不久前发布了这些阶段的概述:stackoverflow.com/questions/1476892/…
  • 是的 - 不确定。我总是使用(大部分)符合 C99 的编译器。不过,看起来 OP 正在使用 C99 编译器,因为 // as cmets 在 C89 中不存在。
  • 我遇到的许多 C 编译器都支持 C++/C99 '//' cmets,即使它们不支持 C99 中的任何其他内容。
  • @Novelocrat:正如我所说,如果您使用的是 C99,那么您是安全的。但从技术上讲,几乎没有完全兼容的 C99 编译器(例如,MS 和 GNU 都不是 100% 兼容的)。
【解决方案2】:

如 C99 标准中翻译阶段的 this copy-n-pasted decription 所述,在翻译阶段 3 中会删除 cmets(它们被单个空格替换),而在阶段 4 中处理预处理指令并扩展宏。

在 C90 标准中(我只有硬拷贝,所以没有复制粘贴)这两个阶段以相同的顺序发生,尽管翻译阶段的描述在某些细节上与 C99 标准略有不同- 在处理预处理指令和扩展宏之前,cmets 被删除并替换为单个空白字符这一事实没有什么不同。

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

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

除了在字符常量、字符串字面量或注释中,字符 // 引入包含所有多字节字符的注释,但不包括 下一个换行符。

C++ 标准说 (2.7):

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

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

但由于您遇到了一个有问题的翻译器,您可能希望将宏定义更改为:

#define foo(x) /* junk */

解决该错误。

但是(我在这里偏离了主题......),因为在处理 cmets 之前发生了行拼接(换行之前的反斜杠),您可能会遇到类似这样的讨厌代码:

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



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

    return 0;
}

这可能会让写它的人感到惊讶。

或者更好的是,尝试以下由喜欢盒子式 cmets 的人(当然不是我!)编写的:

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

取决于你的编译器是否默认处理trigraphs(编译器应该这样做,但由于三元组几乎让所有遇到它们的人都感到惊讶,所以一些编译器决定默认关闭它们),你可能会也可能不会你想要的行为——当然是任何行为。

【讨论】:

  • 或将注释移出#define 行。
  • 问题的重点是junk是实际代码,在不用于调试时被注释掉。
  • 这个答案非常好,并且包含真正帮助我的真理宝石 - 在宏中使用/* */-style cmets,我很安全。
【解决方案3】:

根据MSDN,在分词阶段将cmets替换为单个空格, 这发生在扩展宏的预处理阶段之前。

【讨论】:

    【解决方案4】:

    永远不要把 // cmets 放在你的宏中。如果必须放置 cmets,请使用 /* */。另外,你的宏有错误:

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

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

    if (some condition)
        foo(x);
    

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

    【讨论】:

    • junk 是有时是宏主体的代码。我的前辈没有那么小心。
    • 你能澄清一下你的意思吗?没有具体的例子就很难给出建议。
    【解决方案5】:
    #ifdef _TEST_
    #define _cerr cerr
    #else
    #define _cerr / ## / cerr
    #endif
    
    • 适用于某些编译器 (VC++)。当_TEST_未定义时,

      _cerr ...

      将被注释行替换

      // cerr ...

    【讨论】:

      【解决方案6】:

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

      1. 扩展宏
      2. 再次脱衣

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

      【讨论】:

      • 优点 - 这对于多阶段预处理可能会变得复杂(大多数情况下完全避免使用“包含守卫”,但有一些有趣的好处,尤其是在依赖解析方面。)
      猜你喜欢
      • 2018-01-04
      • 1970-01-01
      • 2020-10-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-01
      相关资源
      最近更新 更多