30

我正在阅读这个问题以找出 Java 虚拟机和 .NET CLR 之间的区别,而 Benji 的回答让我想知道为什么首先需要虚拟机。

根据我对 Benji 解释的理解,虚拟机的 JIT 编译器将中间代码解释为在 CPU 上运行的实际汇编代码。之所以必须这样做,是因为 CPU 通常具有不同数量的寄存器,并且根据 Benji 的说法,“有些寄存器是专用的,每条指令都希望其操作数位于不同的寄存器中。” 这是有道理的,因此需要像虚拟机这样的中间解释器,以便可以在任何 CPU 上运行相同的代码。

但是,如果是这样的话,那么我不明白为什么编译成机器代码的 C 或 C++ 代码能够在任何计算机上运行,​​只要它是正确的操作系统。那么为什么我在使用 Pentium 的 Windows 机器上编译的 C 程序能够在使用 AMD 的其他 Windows 机器上运行?

如果 C 代码可以在任何 CPU 上运行,那么虚拟机的目的是什么?是否可以在任何操作系统上运行相同的代码?我知道 Java 在几乎任何操作系统上都有 VM 版本,但是除了 Windows 之外,还有其他操作系统的 CLR 吗?

还是我还缺少其他东西?操作系统是否对其运行的汇编代码进行其他解释以使其适应特定的 CPU 或其他东西?

我很好奇这一切是如何运作的,因此将不胜感激。

注意:我不只是在 JVM 与 CLR 问题中将我的查询作为评论发布的原因是因为我还没有足够的积分来发表评论 =b。

编辑:感谢所有伟大的答案!所以似乎我缺少的是,尽管所有处理器都有差异,但有一个共同的标准化,主要是 X86 架构,它提供了足够多的通用特性集,以便在一个 X86 处理器上编译的 C 代码大部分都可以工作在另一个 X86 处理器上。这进一步证明了虚拟机的合理性,更不用说我忘记了垃圾收集的重要性。

4

10 回答 10

42

AMD 和英特尔处理器使用相同的指令集和机器架构(从机器代码执行的角度来看)。

C 和 C++ 编译器编译为机器代码,并带有适合其目标操作系统的标头。一旦编译,它们就不再以任何方式、形状或形式与编译它们的语言相关联,并且仅仅是二进制可执行文件。(有些工件可能会显示它是用什么语言编译的,但这不是重点)

因此,一旦编译,它们就会与机器(X86、intel 和 amd 指令集和架构)和操作系统相关联。

这就是为什么它们可以在任何兼容的 x86 机器和任何兼容的操作系统(win95 到 winvista,对于某些软件)上运行的原因。

但是,它们无法在 OSX 机器上运行,即使它在英特尔处理器上运行 - 二进制文件不兼容,除非您运行其他仿真软件(例如并行程序或带有 Windows 的 VM)。

除此之外,如果你想在 ARM 处理器、MIPS 或 PowerPC 上运行它们,那么你必须运行一个完整的机器指令集模拟器,它将 X86 中的二进制机器代码解释为你正在运行它的任何机器。

将其与 .NET 进行对比。

.NET 虚拟机的制造就好像世界上有更好的处理器——理解对象、内存分配和垃圾收集以及其他高级构造的处理器。这是一台非常复杂的机器,现在不能直接在硅片中构建(具有良好的性能),但可以编写一个仿真器,使其能够在任何现有处理器上运行。

突然之间,您可以为要在其上运行 .NET 的任何处理器编写一个特定于机器的模拟器,然后任何 .NET 程序都可以在其上运行。无需担心操作系统或底层 CPU 架构 - 如果有 .NET VM,那么软件就会运行。

但是让我们更进一步——一旦你有了这种通用语言,为什么不制作将任何其他书面语言转换成它的编译器呢?

因此,现在您可以拥有一个 C、C#、C++、Java、javascript、Basic、python、lua 或任何其他语言编译器来转换书面代码,以便它可以在这个虚拟机上运行。

您已经将机器与语言分离了 2 度,并且无需太多工作,您就可以让任何人编写任何代码并让它在任何机器上运行,只要存在编译器和 VM 来映射这两种分离度.

如果您仍然想知道为什么这是一件好事,请考虑早期的 DOS 机器,以及微软对世界的真正贡献是:

Autocad 必须为每台可以打印的打印机编写驱动程序。莲花 1-2-3 也是如此。事实上,如果您想打印您的软件,您必须编写自己的驱动程序。如果有 10 台打印机和 10 个程序,则必须分别独立编写 100 段基本相同的代码。

windows 3.1 试图完成的(连同 GEM 和许多其他抽象层)是让打印机制造商为他们的打印机编写一个驱动程序,而程序员为 windows 打印机类编写一个驱动程序。

现在有 10 个程序和 10 台打印机,只需要编写 20 段代码,而且由于代码的 microsoft 方面对每个人来说都是一样的,那么来自 MS 的示例意味着您几乎没有工作要做。

现在一个程序不仅限于他们选择支持的 10 台打印机,而是所有制造商在 windows 中提供驱动程序的打印机。

同样的问题也发生在应用程序开发中。因为我不使用 MAC,所以我无法使用一些非常简洁的应用程序。有大量重复(我们真的需要多少世界级的文字处理器?)。

Java 旨在解决这个问题,但它有很多限制,其中一些并没有真正解决。

.NET 更接近,但没有人在为 Windows 以外的平台开发世界级的 VM(mono 是如此接近......但并不完全在那里)。

所以...这就是我们需要虚拟机的原因。因为我不想仅仅因为他们选择了不同于我自己的操作系统/机器组合而将自己限制在较小的受众中。

-亚当

于 2009-01-31T02:08:38.217 回答
8

您认为 C 代码可以在任何处理器上运行的假设是不正确的。像寄存器和字节序这样的东西会使编译的 C 程序在一个平台上根本无法运行,而它可能在另一个平台上运行。

但是,处理器之间存在某些相似之处,例如,Intel x86 处理器和 AMD 处理器共享一组足够大的属性,大多数针对其中一个编译的代码将在另一个上运行。但是,如果您想使用特定于处理器的属性,那么您需要一个编译器或一组库来为您执行此操作。

至于为什么你想要一个虚拟机,除了它会为你处理处理器差异的声明之外,还有一个事实是虚拟机提供的代码服务对于今天用 C++ 编译的程序(非托管)是不可用的。

提供的最突出的服务是垃圾收集,由 CLR 和 JVM 提供。这两个虚拟机都免费为您提供这项服务。他们为您管理内存。

还提供了诸如边界检查、访问违规(虽然仍然可能,但它们非常困难)之类的东西。

CLR 还为您提供了一种代码安全形式。

这些都不是作为基本运行时环境的一部分提供给许多其他不能在虚拟机上运行的语言。

您可能会通过使用库来获得其中的一些,但这会迫使您进入使用库的模式,而在 .NET 和 Java 中,通过 CLR 和 JVM 提供给您的服务在访问方面是一致的。

于 2009-01-31T01:56:34.917 回答
5

从本质上讲,它允许“托管代码”,这正是它所说的 - 虚拟机在运行时管理代码。这样做的三个主要好处是即时编译、托管指针/垃圾收集和安全控制。

对于即时编译,虚拟机监视代码执行,因此随着代码运行得更频繁,它会被重新优化以更快地运行。您不能使用本机代码执行此操作。

托管指针也更容易优化,因为虚拟机在它们运行时跟踪它们,根据它们的大小和生命周期以不同的方式管理它们。在 C++ 中很难做到这一点,因为仅阅读代码就无法真正判断指针将去往何处。

安全性是不言自明的,虚拟机阻止代码做它不应该做的事情,因为它正在监视。我个人认为这可能是微软为 C# 选择托管代码的最大原因。

基本上我的观点是,因为虚拟机可以实时观察代码,它可以做一些事情,让程序员的生活更轻松,让代码更快。

于 2009-01-31T02:05:08.957 回答
5

大多数编译器,甚至本机代码编译器,都使用某种中间语言。

这样做主要是为了降低编译器的构建成本。世界上有许多 (N) 种编程语言。世界上也有很多(M)个硬件平台。如果编译器在不使用中间语言的情况下工作,则需要编写以支持所有硬件平台上的所有语言的“编译器”总数将是 N*M。

但是,通过定义一种中间语言并将编译器分成两部分,前端和后端,前端将源代码编译为 IL,后端将 IL 编译为机器代码,您可以只写N+M 编译器。这最终会节省大量成本。

CLR / JVM 编译器和本机代码编译器之间的最大区别在于前端和后端编译器相互链接的方式。在本机代码编译器中,这两个组件通常组合成同一个可执行文件,并且当程序员在 IDE 中点击“构建”时,这两个组件都会运行。

使用 CLR / JVM 编译器,前端和后端在不同的时间运行。前端在编译时运行,生成实际交付给客户的 IL。然后将后端包含在运行时调用的单独组件中。

所以,这就引出了另一个问题,“将后端编译延迟到运行时有什么好处”?

答案是:“这取决于”。

通过将后端编译延迟到运行时,可以发布一组可以在多个硬件平台上运行的二进制文件。它还使程序可以利用后端编译技术的改进而无需重新部署。它还可以为有效实现许多动态语言特性提供基础。最后,它提供了在单独编译的动态链接库 (dll) 之间引入安全性和可靠性约束的能力,这在前期机器代码编译中是不可能的。

但是,也有缺点。实现广泛的编译器优化所需的分析可能很昂贵。这意味着“JIT”后端通常会比前端后端做更少的优化。这可能会损害性能。此外,在运行时调用编译器的需要也增加了加载程序所需的时间。使用“前期”编译器生成的程序没有这些问题。

于 2009-02-02T02:49:47.727 回答
3

首先,机器代码不是 CPU 的最低指令形式。今天的 x86 CPU 本身使用微码将 X86 指令集解释为另一种内部格式。唯一真正编写微码的人是芯片开发工程师类型,他们忠实而轻松地模拟传统的 x86 指令芯片,以使用当今的技术实现最高性能。

由于它们带来的强大功能和特性,开发人员类型一直在添加额外的抽象层。毕竟,更好的抽象允许更快、更可靠地编写新应用程序。企业并不关心他们编码的内容或方式,他们只是希望能够可靠、快速地完成工作。如果应用程序的 C 版本花费的时间少了几毫秒,但最终花费了两倍的时间来开发,这真的很重要吗?

速度问题几乎不是一个争论,因为许多服务于数百万人的企业应用程序是用 Java 等平台/语言编写的——例如 GMail、GMaps。忘记哪种语言/平台最快。更重要的是您使用正确的算法并编写有效的代码并完成工作。

于 2009-01-31T02:35:57.973 回答
2

AMD 和 Intel 处理器都具有 x86 架构,如果您想在不同的架构上运行 c/c++ 程序,您必须为该架构使用编译器,相同的二进制可执行文件不会在不同的处理器架构上运行。

于 2009-01-31T01:49:14.590 回答
2

我知道 Java 在几乎任何操作系统上都有 VM 版本,但是除了 Windows 之外,还有其他操作系统的 CLR 吗?

单核细胞增多症

于 2009-01-31T01:55:06.480 回答
2

以一种非常简单的方式,那是因为 Intel 和 AMD 实现了相同的汇编语言,具有相同数量的寄存器等等......

因此,您的 C 编译器编译代码以在 Linux 上运行。该程序集使用的是 Linux ABI,因此只要编译程序在 Linux、x86 程序集上运行并且具有正确的函数签名,那么一切都很好。

现在尝试获取已编译的代码,并坚持下去,比如 Linux/PPC(例如,旧 iBook 上的 Linux)。那是行不通的。因为 JVM 已经在 Linux/PPC 平台上实现了,所以 Java 程序在哪里。

现在的汇编语言基本上是程序员可以编程的另一种接口。x86(32 位)允许您访问 eax、ebx、ecx、edx 用于通用整数寄存器,以及 f00-f07 用于浮点。在幕后,CPU 实际上还有数百个寄存器,并将这些东西混在一起以挤出性能。

于 2009-01-31T01:56:13.607 回答
0

你的分析是对的,java 或 C# 可以设计为直接编译以在任何机器上运行,如果他们这样做可能会更快。但是虚拟机方法可以完全控制代码运行的环境,VM 创建一个安全沙箱,只允许具有正确安全访问权限的命令执行可能具有破坏性的代码——例如更改密码或更新 HD 引导扇区。还有许多其他好处,但这就是杀手锏。您无法在 C# 中获得 StackOverflow ...

于 2009-01-31T01:54:43.157 回答
0

我认为你的问题的前提是有效的——你肯定不是第一个问这个问题的人。因此,请查看http://llvm.org以了解另一种方法(现在是一个正在运行的项目?或由 Apple 赞助)

于 2009-01-31T03:43:04.140 回答