为什么 C 没有成为独立于平台的语言?

Why was C not made a platform independent language?

最近看了编译器设计的龙书。它提到编译器将中间代码生成作为其生成机器独立代码的阶段之一。那为什么C没有像java一样发展成独立于平台的语言?

简答:因为当时不可行

长答案:Java平台是一个语言+虚拟机,Java代码编译成一个叫做ByteCode的东西,然后虚拟机可以拿这个字节码(类似于汇编语言)并在运行时将其翻译成相关命令,即本地机器可以理解的机器指令。

每个体系结构都有自己的指令集,这意味着 ARM 体系结构将无法理解为 x86 体系结构编译的代码。

在C中,c代码直接编译成机器指令,然后由本地机器执行这些指令。 要获得像 Java 这样的行为,您将需要某种解释器来读取 C 并在运行时将其转换为机器代码,这不是一项便宜的任务,而且对于当时的计算机来说太多了(c是在 1972 年发明的)当然,另一种实现方式是让用户在使用程序之前编译您的程序,这可能很好,但可能会让您的源代码对客户端可见,这是不需要的。 希望这能澄清一些事情。

除了留下一些东西 implementation-defined(实际上这主要是 platform/ABI-defined,但严格来说不一定), C 主要是 platform-independent 语言。事实上,有一些 C 语言的实现(例如 emscripten)可以在任何具有合适 运行 时间环境的机器平台上以 运行 的形式产生输出。如果用 C 编写的软件对语言的 implementation-defined(或更糟,未定义)方面做出假设,那么它可能无法在某些 implementations/machines 上工作,但通常原因更多的是 API/environment/library 假设(比如假设 POSIX,或 Windows,或油嘴滑舌)而不是对语言本身做出不可移植的假设。

C 最初是作为一种 "Write-Once, Compile-Anywhere" 语言设计和编写的,这在当时是他们所能达到的最接近通用语言的水平。

处理器和架构截然不同,资源又是如此之少,以至于通用虚拟机的想法(就像 Java 那样)是不可能的。

单个 code-base 可以通过编译器 运行,然后您在任何目标平台上拥有相同软件的想法非常不可思议。

C 最初是为涉及特定机器的特定用例而设计的。虽然它大致基于通过 platform-independent 虚拟机实现的 BCPL 语言,但 C 的目标是能够编写 low-level 代码,例如操作系统,这意味着它需要能够利用目标机器的特定功能,特别是它直接寻址单个字节的能力。相比之下,BCPL的底层架构果断word-oriented.

贝尔实验室能够用他们的新语言 (C) 快速重新实现 Unix 操作系统这一事实无疑促成了它的流行。 (至少,这就是我最初学习它的原因。)为了允许该语言更广泛地传播,编译器的一个版本是更紧密地遵循龙书中概述的架构编写的,初始生成的虚拟机代码是然后用于为目标机器生成代码。这个便携式 C 编译器多年来一直是一个参考实现,并且继续可用。

与 C 同时代的其他语言,尤其是 Pascal,也使用了针对平台独立虚拟机的策略,并且曾经将虚拟机代码称为 "P-Code" 是很常见的,因为这就是 Niklaus Wirth 的 Pascal 项目称为他们的目标架构。

虽然 GCC 本身不使用虚拟机,但它确实从生成 liw-level machine-independent 内部表示开始,简化了将编译器移植到新架构的任务。当然,Clang 编译器会生成 LLVM(low-level 虚拟机)代码,这些代码可以转译为各种具体的机器代码,或者直接解释。

龙书所描述的是以下过程:

  1. 将源代码编译成中间machine-independent字节码格式
  2. 对该 IR 进行优化和分析
  3. 将 IR 翻译成目标平台的实际机器码

这样做的好处是,如果您想支持其他系统,只需为第 3 步添加一个新的代码生成器,而无需触及第 1 步和第 2 步。

所有常见的 C 编译器都是这样工作的。所以如果你的问题是"Why don't C compilers do what the Dragon Book describes?",答案是:"They do".

现在你提到了Java。 Java 编译器的作用如下:

  1. 将Java代码编译成Java字节码。就Java编译器而言,这不是中间格式,而是实际的目标语言。
  2. 结束

现在要运行这个字节码你需要一个JVM,它解释and/orJIT-compiles字节码吧。优化和分析通常发生在 JIT-compilation 期间。这不是龙书上描述的流程

从语言实现者的角度来看,这不会改变支持新目标系统的工作量。您不再需要更改编译器,而是必须更改 JVM:您不必向 javac 编译器添加新后端,而是向 JIT-compiler 添加新后端。努力程度基本保持不变。

主要区别在于 Java 程序员:您现在无需为每个目标平台编译程序并为每个平台分发程序包,您现在可以编译一次代码并将生成的程序包提供给每个人。现在编写您的代码的人 运行 需要安装 JVM 才能使用该包,因此您基本上将工作从程序员转移到最终用户,但安装 JVM 只需要做一次(并非针对每个 Java 您想要 运行 的程序)。

所以 "write once, compile everywhere" 现在是 "compile once, run everywhere"。

那么为什么 C 没有做 Java 做的同样的事情呢?表现。解释字节码很慢(与 运行ning 编译代码相比)并且 JIT-compilation 导致增加 start-up 时间。