【问题标题】:How do languages like C# and Java avoid C/C++-like independent compilation?C#、Java 等语言如何避免类似 C/C++ 的独立编译?
【发布时间】:2009-03-28 20:23:33
【问题描述】:

在我的编程语言课上,我正在写一篇关于语言设计史上一些重要人物的论文的研究论文。 CAR Hoare 的一篇让我觉得很奇怪,因为它反对 C 和后来的 C++ 中使用的独立编译技术,甚至在 C 流行之前。

既然这主要是为了加快编译时间的优化,那么 Java 和 C# 是什么让它们能够避免依赖独立编译?它是一种编译器技术,还是语言中的元素有助于实现这一点?还有其他编译语言在它们之前使用过这些技术吗?

【问题讨论】:

    标签: c# java compiler-construction programming-languages


    【解决方案1】:

    简答:Java 和 C# 不避免单独编译;他们充分利用它。

    它们的不同之处在于它们不需要程序员在编写可重用库时编写一对单独的头文件/实现文件。用户编写一个类的定义一次,编译器从该单个定义中提取与“头”等效的信息,并将其作为“类型元数据”包含在输出文件中。因此,输出文件(Java 中包含 .class 文件的 .jar 或基于 .NET 语言的 .dll 程序集)是单个包中二进制文件和标头的组合。

    然后当另一个类被编译并且它依赖于第一个类时,它可以查看元数据而不必查找单独的包含文件。

    碰巧它们针对的是虚拟机而不是特定的芯片架构,但这是一个单独的问题;他们可以将 x86 机器代码作为二进制文件放入,并且仍然在同一个文件中保留类似标题的元数据(这实际上是 .NET 中的一个选项,尽管很少使用)。

    在 C++ 编译器中,通常会尝试使用“预编译头文件”来加快编译速度。 .NET .dll.class 文件中的元数据很像预编译的标头 - 已经解析和索引,可以快速查找。

    结果是,在这些现代语言中,有一种模块化方式,它具有完美组织和手动优化的 C++ 模块化构建系统的特征 - 非常漂亮,说起来 ASFAC++B

    【讨论】:

      【解决方案2】:

      IMO,这里最大的因素之一是 java 和 .NET 都使用中间语言;这意味着编译单元(jar/程序集)作为先决条件包含许多关于类型、方法等的表达元数据;这意味着它已经很方便地进行了参考检查。无论如何,运行时仍然会检查,以防你拉一个快速的;-p

      这与支撑 COM 的 MIDL 相距不远,尽管 TLB 通常是一个单独的实体。

      如果我误解了你的意思,请告诉我...

      【讨论】:

      • 相信你正确地理解了我。我基本上是指 C/C++ 中的每个源文件都是其自己的单独编译单元的想法。在 C# 或 Java 中似乎并非如此。
      【解决方案3】:

      您可以认为 java .class 文件类似于 C/C++ 中的预编译头文件。本质上,.class 文件是 C/C++ 链接器所需的中间形式以及包含在标头中的所有信息(Java 只是没有单独的标头)。

      在另一篇文章中发表您的评论:

      “我的意思是 每个源文件都是自己的 C/C++ 单独的编译单元。这 似乎情况并非如此 C# 或 Java。”

      在 Java 中(我不能说 C#,但我认为它是相同的)每个源文件都是它自己的单独编译单元。我不知道为什么你会认为它不是......也许我们对编译单元有不同的定义?

      【讨论】:

        【解决方案4】:

        它需要一些语言支持(否则,C/C++ 编译器也会这样做)

        特别是,它要求编译器生成自包含模块,这些模块公开元数据,其他模块可以引用以调用它们。

        .NET 程序集就是一个简单的例子。一个项目中的所有文件编译在一起,生成一个dll。 .NET 可以查询这个 dll 以确定它包含哪些类型,以便其他程序集可以调用其中定义的函数。

        并且要利用这一点,引用其他模块的语言必须是合法的。

        在 C++ 中,什么定义了模块的边界?该语言指定编译器仅考虑其当前编译单元(.cpp 文件 + 包含的头文件)中的数据。没有指定“我想在模块 Bar 中调用函数 Foo,即使我在编译时没有原型或任何东西”的机制。在文件之间共享类型信息的唯一机制是使用#includes。

        有人提议在 C++ 中添加模块系统,但不会在 C++0x 中。我上次看到,计划是在 0x 出来后考虑将它用于 TR1。

        (值得一提的是,最初使用 C/C++ 中的#include 系统是因为它加速编译。早在 70 年代,它允许编译器以简单的方式处理代码线性扫描。它不必构建语法树或其他此类“高级”功能。今天,表格已经发生转变,它已成为一个巨大的瓶颈,无论是在可用性方面还是在编译速度方面。)

        【讨论】:

        • C 语言的设计更多地基于确保编译在内存有限的机器上的可行性,而不是试图使其快速编译。今天,很少有系统的整个源代码无法轻松放入典型台式机的 RAM 中,但 C 设计的时代,大型机器的 RAM 还不到今天的 1/16,000工作站。开发环境必须在构建速度和最大实际构建大小之间做出权衡。
        【解决方案5】:

        由 C/C++ 生成的目标文件只能由链接器读取,而不是由编译器读取。

        【讨论】:

          【解决方案6】:

          至于其他语言:IIRC Turbo Pascal 有“单元”,您无需任何源代码即可使用。我认为重点是创建元数据以及编译后的代码,然后编译器可以使用这些元数据来确定模块的接口(即函数签名、类布局等)

          C/C++ 的一个问题是,它阻止仅用某种#import 替换#include 也是预处理器,它可以完全改变包含/导入模块的含义/语法等。对于类似 Java 的模块系统,这将非常困难(如果不是不可能的话)。

          【讨论】:

            猜你喜欢
            • 2010-10-13
            • 1970-01-01
            • 1970-01-01
            • 2012-06-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-07-16
            相关资源
            最近更新 更多