C#是部分解释性的还是真正编译成机器码的?

90

这个问题存在很多互相矛盾的信息。有人说C#是编译型语言(因为在运行时,它被编译成中间代码(IL),然后再编译成本地代码),而另一些人则称其为解释型语言,因为它需要.NET才能运行。英文维基百科表示:

许多解释型语言首先被编译成某种虚拟机器码,然后在运行时被解释或编译成本地代码。

所以我非常困惑。有没有人能够清楚地解释这个问题呢?


1
可能是 C# 是解释型的还是编译型的? 的重复问题。 - Cody Gray
在某人发明一种以LLVM IR分发并要求用户安装某个独立的LLVM实例的语言之前,我来了解一下。 - SOFe
我知道这已经很老了,但我认为它仍然相关。这里的大多数讨论来自于“解释语言”与“编译语言”的不同定义。通过(1)澄清OP对这些术语的具体定义或(2)将“解释”与“编译”的问题与有关构建/运行C#代码的可用性/标准的问题分开,可能会改进该问题。对于那些对C#.NET本地预先编译(Native AOT)的进展感兴趣的人,请参见此存储库。 - teichert
15个回答

90

C#被C#编译器编译成IL。

然后,当需要时,这个IL会被即时编译(JIT)成主机机器的本地组合语言。虽然可以编写一个.NET运行时来解释IL,但我仍然认为C#是一种编译语言。


4
为什么很多程序员认为它是解释性语言? - John V
20
就我个人而言,我从未听说过有人称C#为解释型语言。 - jrummell
9
也许他们只是误解了,或者得到的信息不准确。或者也有可能是我错了 :-) - Simon
24
JIT编译 ≠ 解释。 - spender
3
在C++中,有经验的程序员可以选择使用模板而非虚函数来影响生成代码的效率。但在C#中这种情况并不存在。程序员与实际上没有完全编译的代码之间存在太多层级。此外,C++代码不需要随附VM运行时,但是对于C#代码,仍然需要某种解释器。因为它并不是真正编译过的。 - Erik Aronesty
显示剩余5条评论

49

一种纯粹的编译语言具有一些优点,例如速度通常较快,工作集大小也较小。一种纯粹的解释语言也具有一些优点,例如不需要显式编译阶段的灵活性,可以在原地编辑,而且通常更易于可移植性。

一种即时编译语言位于这种情况的中间地带。

这就是为什么我们可能会将即时编译语言视为编译或解释语言,具体取决于我们关心哪个指标,并对其中一个持支持或反对态度的原因之一。

C#也可以在第一次运行时进行编译,就像在ASP.NET中发生的那样,这使得它在这种情况下接近解释,尽管它仍然被编译为IL代码,然后在这种情况下被JIT编译。当然,在这种情况下,它几乎具有解释的所有优点(与经典ASP中使用的VBScript或JScript进行比较),以及大部分编译的优点。

严格地说,没有任何语言是即时编译的,解释的或编译的 qua语言。我们可以将C#NGen到本机代码(尽管如果它执行诸如动态加载程序集的操作,它仍将使用IL和JIT编译)。我们可以为C或C ++编写解释器(几个人已经这样做了)。但是在其最常见的用例中,C#被编译为IL代码,然后被JIT编译,这与解释或编译的经典定义并不完全相同。


1
我同意“没有语言是即时编译、解释或编译为语言”。我看到了一些关于Python的内容,它首先被编译成“byteCode”,然后在运行时由相应操作系统的解释器解释该“byteCode”。这个事实只增加了我对编译、即时编译和解释型语言定义的惊奇。 - RBT
JIT语言可以比本地语言更快。你知道gcc有一个仪器的东西,它在运行时输出性能测量文件,然后将其反馈到gcc中编译更优化的二进制文件吗?JVM和CLR都在不断地执行这个过程。 - John Moser

21

语义和观点过多。

首先:C# 不是解释性语言;CLR 和 JVM 被视为"运行时"或"中间件",但同样的名称也适用于像 Perl 这样的东西。这在关注名称的人中间造成了很多混乱。

引用运行时的"解释器"一词通常意味着现有的代码解释一些非本地代码。有两个大范畴:解析读取原始源代码并采取逻辑动作;字节码执行首先将代码编译为非本地二进制表示,需要更少的 CPU 循环来解释。

Java 最初编译为字节码,然后通过解释器;现在,JVM 读取字节码并即时将其编译为本地代码。CIL 也是如此:CLR 使用即时编译到本机代码。

考虑运行源代码、运行字节码、编译为本机代码、即时编译、将源代码通过编译器运行到即时本机等所有组合的情况。语言是编译还是解释的语义变得毫无意义。

例如:许多解释性语言使用即时字节码编译。C# 编译为 CIL,JIT 编译为本机代码;相比之下,Perl 立即将脚本编译为字节码,然后通过解释器运行这个字节码。你只能在 CIL 字节码格式中运行 C# 组装;你只能在原始源代码格式中运行 Perl 脚本。

即时编译器还运行许多外部和内部的仪表记录。运行时跟踪各种函数的执行,然后调整代码布局以优化其特定执行流的分支和代码组织。这意味着 JIT 代码可以比本地编译的代码(例如通常是 C++ 的,或者像 C# 运行通过 IL2CPP)运行得更快,因为 JIT 调整其优化策略以代码运行时的实际执行情况为依据。

欢迎来到计算机编程的世界。我们决定让它变得非常复杂,然后给每件事都附上不具描述性的名称。目的是为了在没有实际意义的单词定义上创建火焰战争。


JVM会解释字节码,然后将其中一些方法/循环体编译成本地代码。这个编译过程可以与字节码解释并行进行,并且代码可以进行热替换。 - Steves
我找不到相关引用。我的理解是JVM在运行时通过JIT解释字节码——也就是说:当它在当前运行中遇到尚未处理的字节码时,它会通过JIT运行并执行生成的本地代码块。JIT和执行比字节码解释更便宜,同时实现这两个系统非常复杂。 - John Moser
请查看例如https://www.slideshare.net/ZeroTurnaround/vladimir-ivanovjvmjitcompilationoverview-24613146。您所描述的是.NET所做的事情。我所描述的是例如Hotspot JVM所做的事情,而且它非常复杂,这就是为什么它已经开发了一段时间。您需要对字节码进行解释以获取配置文件信息(考虑:哪个if分支大多数时间被采用等),然后在优化编译器中使用它(如果该if分支从未被采用,则不要花费太多资源来优化它)。 - Steves
1
实际上,现在Perl也可以进行JIT编译。 - Erik Aronesty
你可以将分析工具嵌入到本地代码中。gcc 也有类似的分析工具。编译器可以识别循环迭代器并添加代码来比较其进入和退出状态;它可以在分支被执行时设置一个变量等等。这可以用来统计每次 JIT 检查分析数据之间的调用次数、循环计数、分支计数等等。它的成本只有几个周期,远不及解释字节码的成本。 - John Moser
JVM并不总是将字节码编译成本地代码。最常用的JVM称为HotSpot - https://dev59.com/8GQn5IYBdhLWcg3wzZpU - 具有决定是否产生编译开销或解释的策略。该策略在概念上非常简单,它首先进行解释,但如果多次运行方法,则进行JIT编译。它可以计算方法调用次数,因为JVM已经产生了跟踪这些调用的开销(例如堆栈跟踪信息等)。简而言之,在Java中,您的一次性初始化代码可能正在被解释。 - radfast

18
如果你觉得、学到了或者是老派的话,那么一个编译的EXE文件是从源代码转换成机器代码,那么C#就是解释型的。 如果你认为编译意味着将源代码转换为其他代码,比如字节码,那么是的,它是编译的。对我来说,任何需要运行时处理才能在其所构建的操作系统中运行的东西都是解释型的。

12

看这里:http://msdn.microsoft.com/library/z1zx9t92

C# 编写的源代码被编译为符合 CLI 规范的中间语言(IL)。

(...)

当 C# 程序执行时,程序集被加载到 CLR 中,CLR 可能会基于清单中的信息执行各种操作。然后,如果安全要求得到满足,CLR 将执行即时 (JIT) 编译将 IL 代码转换成本地机器指令。


谢谢,如果我理解正确的话,与解释器相关的困惑可能来自需要虚拟机(CRM),但它并不是真正的解释器。 - John V
链接已经挂了;-) - Markus Safar

9

首先,让我们了解解释和编译的定义。

"编译"(指代码时)意味着将代码从一种语言翻译成另一种语言。通常是从人类可读的源代码翻译为目标处理器可以处理的机器码。

"解释"(指代码时)也意味着将代码从一种语言翻译成另一种语言。但这次通常用于将人类可读的源代码转换为中间代码,由虚拟机接收并将其解释为机器码。

只是为了明确
源代码 -> 编译器 -> 机器码
源代码 -> 编译器 -> 字节码 -> 解释器 -> 机器码

任何编程语言理论上都可以被解释编译。通常情况下,Java被编译成字节码,由Java虚拟机解释为机器码。C#通常被解释成字节码,由CLR(公共语言运行时)编译为另一个虚拟机的机器码。
整个过程大多是一种营销噱头。术语“解释”被添加(或至少增加了使用率),以展示“即时编译”的优越性。但他们本可以使用“编译”。这种区别更多地是英语语言和商业趋势的研究,而不是技术本质。

7
在解释器中,没有机器码生成于运行时,只有现有的机器码被执行。按照这个标准,将公共语言运行时称为解释器是完全不正确的。 - Ben Voigt
考虑到Python和C#,我可以在Python中创建模块和代码,并在应用程序中使用它,而无需停止它并重新编译。而在C#中,我必须停止应用程序才能加载新的代码。对我来说,这是最大的区别,特别是考虑到人工智能是否应该自我增长。 - Niklas
祝你好运,以最快的速度运行它。 - Philip

4
C#在其生命周期中既是解释型的,也是编译型的。C#被编译成一种虚拟语言,由虚拟机进行解释。
“编译型语言”这个术语有些模糊不清。从某种意义上说,“编译型语言”是一个错误的称呼,因为编译或解释并不是语言本身的属性,而是运行时环境的属性。
例如,您可以编写一个C语言解释器,但人们通常将其称为“编译型语言”,因为C语言实现会将其编译为机器代码,并且该语言的设计考虑了编译。

3
不,桌面版的.NET虚拟机(称为公共语言运行时)有JIT编译器,没有解释器。相比之下,在微型嵌入式设备上发现的.NET微框架可能不支持运行时代码修改,实际上会解释IL代码。 - Ben Voigt
源代码 -> (编译器)-> 中间语言 -> (解释器)-> 本地代码。我认为JIT是解释器优化。它基本上是在解释器层面上进行“缓存”。如果您愿意,可以称这两个步骤为编译器。 - ajfabbri

3

大多数语言(如果不是全部),需要一个解释器来将它们的脚本翻译成机器代码,以使CPU能够理解和执行它!

每种语言处理翻译过程的方式都不同!

例如,“AutoIt”就可以描述为一种100%解释语言!

为什么?

因为“AutoIt”解释器在执行其脚本时必须始终存在!请参见以下示例:

Loop, 1000
Any-Code

"AutoIt"解释器需要将"Any-Code"翻译成机器码1000次,这就使得"AutoIt"成为一种缓慢的语言!
另一方面,C#处理翻译过程的方式不同,C#的解释器只需在脚本执行前执行一次,之后在脚本执行期间不再需要!
C#的解释器只需要将"Any-Code"翻译一次成机器码,这自动使"C#"成为一种快速的语言!
因此,
- 需要在脚本执行期间使用其解释器的语言是"解释型语言"! - 仅需要在脚本执行前使用其解释器的语言是"编译型语言"!
最后,
- "AutoIt"是一种"解释型语言"! - "C#"是一种"编译型语言"!

2

我相信这是一个相当古老的话题。

从我的角度来看,解释性代码将通过解释器逐行翻译和执行。例如JavaScript,它是一种解释性代码,当一行JavaScript遇到错误时,脚本会停止。

而编译性代码则会经过编译器,一次性将所有代码转换为另一种形式的代码,而不需要先执行它。执行在另一个上下文中进行。


JavaScript 要么像 C# 一样立即进行 JIT 编译,要么像 HotSpot JVM 一样执行,解释直到代码的某些部分变热,此时通过优化编译器进行 JIT 编译。这取决于 JavaScript 引擎。JavaScript 已经超过 10 年没有被解释了。 - Julian Jensen

1
如果我们同意解释器的定义“在计算机科学中,解释器是一种直接执行编写在编程或脚本语言中的指令的计算机程序,无需先将它们编译成机器语言程序。”,那么毫无疑问:C#不是一种解释型语言。

维基百科上的解释器


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接