【问题标题】:Few questions about C++ compilers : GCC, MSVC, Clang, Comeau etc关于 C++ 编译器的几个问题:GCC、MSVC、Clang、Comeau 等
【发布时间】:2011-07-16 10:18:03
【问题描述】:

我有几个关于 C++ 编译器的问题

  • C++ 编译器是否必须为one-pass compiler?标准是否在任何地方谈论它?

  • 特别是 GCC 是一次性编译器吗?如果是,那么为什么它会在this example 中生成以下错误两次(尽管每个错误消息中的模板参数都不同)?

    错误:‘adder item’的声明遮蔽了参数
    错误:‘adder item’的声明遮蔽了参数

一个更一般的问题


有用的链接:

【问题讨论】:

标签: c++ visual-c++ gcc compiler-construction clang


【解决方案1】:

据我所知,30 年前,编译器必须一次性完成,因为磁盘(或磁带)的读写速度非常慢,而且没有足够的内存来保存整个代码(感谢 James关泽)。此外,单遍是脚本/交互式语言的要求。

现在的编译器通常不是一次性的,有几个中间表示(例如 Abstract Syntax TreeStatic Single Assignment Form),代码被转换成然后分析/优化。

如果没有一些中间步骤,C++ 中的某些元素无法解决,例如在类中,您可以引用仅稍后在类主体中定义的成员。此外,需要以某种方式记住所有模板,以便在实例化期间进一步访问。

not 经常发生的情况是,源代码没有被多次解析——没有必要。因此,您应该不会遇到多次报告相同的语法错误。

【讨论】:

  • 过去单次传递之所以重要,是因为主存不够大,无法容纳函数的完整内部表示;如果编译器不是单次通过,则必须从磁盘重新读取数据。或者磁带,当时仍然大量使用。
【解决方案2】:
  1. 不,如果您发现一个使用频率很高的 C++ 单通道编译器,我会感到惊讶。
  2. 不,它会根据您传递的标志进行多次传递,甚至进行不同的优化。

优势(单程):快!因为只需要在编译阶段(因此开始执行)可以非常快速地发生时检查所有源代码。它也是一个很有吸引力的模型,因为它使编译器易于理解并且通常“更容易”实现。 (我曾经做过一次单遍 Pascal 编译器,但不经常遇到它们,而单遍解释器很常见)

缺点(单通):优化、语义/句法分析。有时,单一的代码外观可以让简单的机制在多次传递中轻松捕获的东西通过。 (这就是为什么我们有 JSLint 之类的东西)

优势(多遍):优化、语义/句法分析。甚至像“JRuby”这样的伪解释语言在执行之前也会通过管道编译过程来获取 java/jvm 字节码,您可以考虑这种多遍,并且多次查看代码的不同表示(以及由此产生的优化)可以使它非常快。

缺点(多遍):复杂,有时需要时间(取决于是否使用 AOT/JIT 作为编译方法)

此外,单遍在学术界很常见,可帮助学习编译器设计的各个方面。

【讨论】:

    【解决方案3】:

    该标准对以下方面没有任何要求 编译器是如何实现的。但是你是什么意思 “一程”?今天的大多数编译器只读取输入文件 一次。他们创建了一个内存表示(通常在 某种解析树的形式),并且可以进行多次传递 在那之上。并且几乎可以肯定地多次通过零件 其中。编译器必须“通过”内部 每次实例化模板时的表示形式,例如 例子;没有办法避免这种情况。 G++ 也使 在定义模板时“通过”模板,在任何之前 实例化,然后报告一些错误。 (标准 委员会明确设计了模板,以允许最多 在定义点进行错误检测。这是 在某些情况下要求 typename 背后的动机 地方,例如。)即使没有模板,编译器也会 通常必须对类定义进行两次传递,如果 里面定义了函数。

    关于更一般的问题,我想你会 必须准确定义“一次性”的含义。我不 知道今天任何编译器读取源文件几个 次,但几乎所有节点都会访问 不止一次解析树。这是一次通过还是多次通过?这 在过去,当记忆不存在时,区别更显着 足以在内部维护大部分源代码 表示。像 Pascal 这样的语言,在较小程度上 C,有时被设计为易于使用单个 传递编译器,因为单传递编译器将是 明显更快。今天,这个问题在很大程度上无关紧要, 而现代语言,包括 C++,往往会忽略它;在哪里 C++ 似乎符合一次性编译器的需求,它是 主要是出于 C 兼容性的原因,并且在哪里 C 兼容性不是问题(例如在类定义中),它 经常使声明的顺序无关紧要。

    【讨论】:

    • 您一直在问什么是“单程”或“多程”,但这些都是定义明确的术语。原始问题中提供了链接,不需要其他信息。显然,multi-pass不需要你多次解析源代码,但它需要以某种形式保存整个程序,然后从头开始转换。一通禁止。例如,一次通过不允许使用仅稍后在代码中声明的变量/函数。但在 C++ 中,在类范围内,它允许这样做。
    • 是吗?维基百科在第一段中自相矛盾。从历史上看,一次传递意味着一次传递源代码,但在编译器无法在内存中保存比符号表更多的上下文中。因此,使用一次性编译器编译的程序无法引用稍后在代码中声明的变量。 C++ 允许使用在某些情况下首先声明的变量。但是我知道的所有编译器都只会读取一次源代码。
    • 正如我所说:“多遍不需要你多次解析源代码,但它需要以某种形式保存整个程序,然后从头开始转换。”相反,single-pass 不需要你以任何形式将整个程序保存在内存中。
    • 这不会让我们走得太远? “整个程序”是什么意思?特别是,C++ 要求编译器保持模板和类定义可用,但是一旦函数被解析,它的内存映像就可以被丢弃。并且可以通过仅访问树一次来解析普通函数(尽管通常,可能会多次访问树的一小部分以解决歧义)。
    • 同意...术语“one-pass”或“multi-pass”是较早的计算时代的产物,但像这些旧术语在维基百科中失去其重要性很久之后仍然存在教科书。
    【解决方案4】:

    编译器只需要从上到下查看一次源代码,但这并不意味着它不必多次处理解析的内容。特别是对于模板,它必须实例化具有类型的模板化代码,并且在使用模板(或由用户显式实例化)之前不会发生,这就是重复错误的原因:

    定义模板时,编译器会检测到错误,此时尚未替换类型。当实际实例化发生时,它会替换模板参数并处理结果,这就是触发第二个错误的原因。请注意,如果模板是在第一个定义之后和实例化之前专门化的,那么对于该特定类型,不需要发生第二个错误。

    【讨论】:

      【解决方案5】:

      第一个 C++ 编译器的开发者 Walter Bright 曾表示,他认为没有至少 3 次通过就无法编译 C++。而且,是的,这意味着 3 次全文转换通过源,而不仅仅是遍历内部树表示。请参阅his Dr. Dobb's magazine article,“为什么 C++ 编译这么慢?”因此,找到真正的一次性编译器的任何希望似乎都注定要失败。 (我认为这是 Bright 开发 D 的部分动机,他的 C++ 替代品。)

      【讨论】:

        猜你喜欢
        • 2017-10-17
        • 1970-01-01
        • 1970-01-01
        • 2020-06-03
        • 2019-10-02
        • 1970-01-01
        • 2012-01-16
        • 2018-07-12
        • 1970-01-01
        相关资源
        最近更新 更多