65.9K
CodeProject 正在变化。 阅读更多。
Home

编译语言和解释语言的区别

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.65/5 (20投票s)

2013年12月14日

BSD

16分钟阅读

viewsIcon

181560

downloadIcon

388

本文描述了编译语言和解释语言之间的区别。

介绍  

世界上有成千上万种编程语言。有些被称为编译型语言,用于这些语言的软件被称为编译器。有些被称为解释型语言。用于解释型语言的软件被称为解释器。另一个有趣的现象是,对于每一种编译型语言,都可以构建一个解释器,但反过来则不可能。也就是说,所有的解释型语言都不能成为编译型语言。无论是解释型还是编译型,都不是编程语言本身的属性,而是某些语言的设计使其不适合生成本地代码。本文描述了原生编译型语言和非原生编译型语言的优缺点。这是我的第一篇文章,我还没有太多写作经验。 

 编译型与解释型语言  

原生编译型语言 

原生编译型语言是指有编译器可以将其源代码编译成原生代码的编程语言。原生语言总是可以成为解释型语言。以 C++ 为例,它是一种原生编译型语言。但它也有 CINT、ch interpreter 等解释器。这是因为原生代码的限制。原生编译型语言的设计是为了适应这些限制,而解释型语言则不是。

 

解释型语言和 JIT 编译(非编译成原生代码) 

在某些情况下,源代码由名为解释器的软件逐行执行。解释型语言通常比编译型语言慢,原因有很多。这是因为源代码需要逐行执行。此外,Python、Java、C# 等解释型语言拥有许多高级功能,如动态类型、类型检查、大量的类型信息,这些信息存储在解释器中,会降低解释型应用程序的性能。另一个缺点是,除非解释器嵌入在软件中,否则计算机上必须安装解释器,这可能会增加软件包的大小。例如,Blender 内嵌了 Python 解释器。  

直接执行源代码

解释型语言的源文件由解释器直接执行,无需将其更改为中间代码或字节码形式。PHP、JScript 和 VBScript 等一些 Web 脚本语言基于直接源代码执行。其中一些可能具有即时编译功能,但它们不会将源代码转换为字节码形式。

编译成字节码

有些编译器或框架将某些编程语言的源代码转换为嵌入在可执行文件或任何文件类型中的中间语言。然后,虚拟机或解释器在应用程序运行时执行中间语言。这类应用程序比源文件被解释的应用程序要快。有时,还有另一种执行方式称为即时执行 (JIT),其中字节码在运行时由解释器转换为原生代码并执行。这种方法被证明可以提高解释型语言的性能。尽管如此,它们仍然比原生编译型语言慢。Microsoft 的 .NET Framework 和 Java 的某些实现就是基于这个原理工作的。编译成字节码属于解释型语言的范畴。

解释型和 JIT 编译型语言的优点

通用优点

我们已经讨论了解释型和 JIT 编译型语言的缺点。尽管它们有很多缺点,但它们也有许多优点。首先,解释型语言节省了编译时间,从而加快了开发速度。这使它们成为科学和数学计算的理想选择。它们还提供了许多编译型语言无法访问的功能。它们也非常适合脚本编写。例如,Microsoft Word 通过解释 Visual Basic 代码提供了脚本功能。如果您编写一个需要与用户无缝交互的应用程序,那么脚本编写就是其中一种选择。它还可以自动化任务,减少其执行时间。JScript、VBScript、PHP 等解释型语言是 Web 编程的良好选择。这是因为它们无法针对每个平台和设备进行编译,而在加载时。假设一个网页在 Linux 机器上加载,并且包含 C++ 代码,那么它无法为该机器进行编译。因此,解释型语言在 Web 编程中起着不可或缺的作用。只要在支持它们的浏览器中查看,它们就可以在任何设备或机器上运行。例如,在计算机上使用 Google Chrome 浏览器查看包含 JScript 的网页,在 Android 平板电脑上查看会产生相同的结果。Java 通常被认为是解释型语言。Java 源代码被转换为字节码。Java 在桌面编程方面并不流行,但在 Web、手机编程、Android 编程等方面有广泛应用。例如,以 Google 的 Android OS 为例。它有一个 Linux 内核,因此可以使用 ARM 编译器创建软件。但 Google 没有这样做。Android 有一个名为 Dalvik 的虚拟机,它执行 Dalvik 字节码。出于某种原因,他们没有嵌入 Java VM。他们提供了将 Java 字节码转换为 Dalvik 字节码的工具。他们之所以选择 Java 而不是 C++,是因为 Java 拥有一个庞大的社区,这可能导致其应用商店中的应用数量迅速增加。Java 也是一种强大的面向对象编程语言。在 Dalvik VM 上运行的 Android 应用性能并不完全。尽管没有可见的性能影响,但 Google 发布了 Android Native Development Kit (NDK),允许用户通过原生代码加速关键代码段。Android NDK 提供了一种使用 C 和 C++ ARM 编译器创建应用部分的方法。

扩展您的应用程序 

脚本语言还为编写应用程序的扩展或插件提供了一个很好的平台,而无需动态加载动态链接库或共享对象。这可以减少编写特定应用程序插件的难度。编写插件的传统方法是创建 DLL,这需要编译器以及应用程序使用的库。许多现代应用程序都提供了使用 Python 等脚本语言创建插件的方法。 

跨平台支持

跨平台编程一直很困难,而依赖于单一平台的软件可能会严重影响受众范围。如果您使用编译型语言创建软件,您必须将源代码从一台机器移动到另一台机器,并在该机器上编译源代码。但是,如果代码本身不是平台相关的,那么由解释器执行源代码就不会受限于单一平台。生成平台无关的代码并非总是问题。假设您在 Windows 上使用 Python 创建了一个应用程序,并希望它具有平台独立性,那么就不要使用仅在 Windows 上可用的平台特定功能,如组件对象模型 (COM)。Java 遵循“一次编写,随处运行”的策略。但 .NET 不是跨平台的。它更适合 Windows,但 .NET 有许多变体,如 dotGNU,它们是跨平台的。甚至还有一个基于 .NET 的 Android 框架!!

Java 和 .NET 支持反射  

Java 和 .NET 语言支持一种出色的功能,称为反射。使用反射,甚至可以在运行时创建和使用方法或类。甚至有十几种 .NET 语言的出现就是因为反射功能。它们支持无缝的对象序列化,而无需任何外部库。甚至可以使用反射反汇编程序集。但是,应该知道,任何 .NET 和 Java 程序集都可以使用相同的反射功能进行重新工程化。可以通过使用混淆器来克服这种危险。在此页面上可以找到 .NET 混淆器列表。.NET 混淆器。

广泛的设备 

在性能受限的各种设备中使用也是非原生代码语言的另一个优势。以 Java Micro Edition 为例,它广泛用于各种设备,甚至是非智能手机。它允许使用强大的面向对象编程语言为不先进的设备创建应用程序。Java 安装程序声称 Java 在超过 30 亿台设备上运行。Python 解释器可用于 Android 设备(QPython for Android),使用户能够使用方便的设备执行高级计算。在没有操作系统的 J2ME 设备上,没有人能够将应用程序编译成本地代码并运行。 

更小的可执行文件和软件包

显而易见,解释型应用程序的可执行文件尺寸比编译型语言的小得多。有趣的是,C# 应用程序嵌入了中间语言,而不是机器语言。Java 也是如此,这可以显著减小可执行文件的尺寸。没有人会否认 Python 库,如 wxWidgets,是用 C++ 创建的,并通过 SWIG 连接。但是,Python 的 wxWidgets 库的尺寸比 C++ 的要小。

易于调试

错误是程序员的头痛问题。.NET 语言和其他解释型语言提供了方便的设施,可以轻松捕获错误。它们会抛出异常,并在对话框中清楚地显示异常的详细信息。与只指示错误存在的原生代码不同,托管代码甚至可以显示未初始化对象发生的错误,并指示对象的名称。这可以轻松消除错误。例如,如果您的应用程序分发时有错误,并且用户看到错误消息,那么就可以通知用户发送错误报告。这使我们能够轻松地在源代码中定位错误,而不是费力地搜索它。 

并行代码执行 

这是解释型语言独有的功能之一。此功能在 C# 或 Java 中不可用。Python、J、R 等编程语言有控制台或 GUI 命令行软件,代码可以即时执行。它们也被称为“shell”。它们也会即时报告错误。这可以为学习提供具体的便利。 

编译型语言的优点

速度

编译型语言由于由计算机直接执行,因此始终被认为速度更快。速度和性能会影响程序员的选择。特别是对于大型项目,速度和性能是必不可少的。糟糕的速度会破坏用户体验并惹恼他们。C 被认为是仅次于汇编语言的最快的编程语言。据信,在某些情况下,C 比 C++ 快。一个网站甚至说,用 Python 实现的一些算法可能比用 C++ 编写的等效算法慢十倍。显而易见,速度并不总是最重要的。有些地方速度是次要的。

原生应用程序更安全

即使原生应用程序可以被反汇编,汇编代码也不是非常清晰,源代码也不容易获得。例如,可以使用一些工具从 .NET 程序集中生成 C# 代码,但无法从可执行文件中生成 C++ 代码。 

大型软件用编译型语言编写

由于编译型语言提供的速度和性能,大型软件和数百万美元的项目通常用编译型语言编写。许多大型软件,拥有庞大的代码库,如 Office 套件、IDE、编译器、游戏、时间关键型和任务关键型应用程序,都是用编译型语言编写的。您甚至可以在计算机上观察到许多软件是原生编译的。您正在查看此页面的网络浏览器可能也是用编译型语言编写的。浏览器是原生编译的,因为它们需要速度。但大型软件的某些部分可能用解释型语言编写。值得注意的是,您的浏览器使用 JScript、VBScript、PHP 等许多解释型语言来查看网页。

反射并非不可能 

在编译型语言中,反射并非不可能。可以使用第三方库,如GNU lightningBoost Serialization 等。因此,反射在编译型语言中并非不可能。它可以使我们不必为每个持久化数据创建 XML 或任何其他形式的 IO 例程来以文本形式保存数据,这非常耗时。一些程序员喜欢以二进制形式保存数据,而不是简单地以文本形式保存。但是,编写能够发出机器代码的原生语言编译器是一个高级步骤,尽管有一些库,如ASMJIT,可用于实时机器代码生成。

与 .NET、Java 和 Python 的互操作性是可能的

原生编译型应用程序可以使用 .NET(通过 COM)、Java 库和 Python 源代码。因此,如果您在解释型语言上有大量投资并计划转向编译型语言,您仍然可以使用为 .NET、Java 和 Python 创建的库。有一种选择是在 MFC 应用程序中使用 .NET 控件。可以通过创建 COM 包装器从任何程序访问 .NET 程序集。

为什么有些解释型语言没有编译器?

本文已讨论过由于在转换为机器语言方面存在一些限制,因此无法为某些解释型语言创建编译器。这并不意味着每种解释型语言都不能有编译器。Java 有一个原生代码编译器(GNU Java Compiler)。Python 没有可以编译原生代码为机器语言的原生编译器。Python 将源代码转换为扩展名为 (.pyc) 的字节码文件。但是源代码可以转换为可执行文件,尽管如此,它仍然是被解释的。在这种情况下,代码嵌入在可执行文件中,然后由嵌入其中的 Python 解释器执行。这没有任何影响,但有助于使程序成为闭源。限制编译成本地代码的一些特性是: 

在 Python 中,函数在函数执行结束之前不会指示其返回类型。在实际执行例程之前,解释器甚至不知道对象是否会被返回。这种动态类型在机器代码中是不可能的。考虑以下程序 

import random;
def method():
    generated = random.randrange(6); 
	
    if(generated / 2 == 0):
      return "It is even";
    if(generated / 2 == 1):
      return 1;	  
    return 0; ## Impossible
	
print(method());
print(method());
print(method());
输出如下
It is even
It is even
1

每次执行时,输出可能不同,因此不可能生成此程序的汇编语言表示。

C++ 或 D 编程语言中的模板怎么样?  

 C++ 或 D 中的模板与 Python 程序中的动态类型不同。它们只不过类似于宏。对象的类型在编译时定义,而不是在运行时定义,如上述程序所示。考虑以下 C++ 源代码。 

#include <iostream>
#include <conio.h>
#include <typeinfo>

using namespace std;

#define INT 0 // Return an integer
#define STR 1// Return a string  
	
template <class T>
void method(T val)
{
   if(!strcmp(typeid(val).name(), "int"))
   { 
      cout<<"The variable is an integer"<<endl;
   }
   else if(!strcmp(typeid(val).name(), "double"))
   {
      cout<<"The variable is a double"<<endl;
   }
}

void main()
{
   method<int>(3);
   method<double>(2.4);
   getch();
}

  上面的程序是 C++ 函数模板的一个示例,它将产生以下输出,

该变量是整数 

该变量是双精度数  

  • 第一次调用该方法时,模板参数是“int”,因此在编译函数时,C++ 编译器会将所有类型名称为 T 的对象更改为“int”,因此类型标识函数将变量“var”的类型告知为“int”。这些都只发生在编译时。

  • Python 的某些函数,如“eval()”,始终需要 Python 解析系统和运行时系统。
  • 类转换 - C++ 的方式

  • 在 C++ 中,类的编译方式与 C# 或 Java 完全不同。考虑以下 C++ 代码
    class ABC
    {
    public:
        int a;
    	ABC()
    	{
    	}
    	void Method1()
    	{
    	
    	}
    	void Method2()
    	{
    	
    	}	
    };
    int main()
    {
    	ABC abc;
    	abc.a = 10;
    	abc.Method1();
    	return 0;
    }
    
    该代码包含一个名为“ABC”的类,该类有一个“Method1”,并且该类已在 main() 函数中实例化。我使用 MinGW C++ 编译器通过“-S”选项(区分大小写)将源代码转换为汇编表示,汇编如下: 
    .file	"m.cpp"
    	.def	___main;	.scl	2;	.type	32;	.endef
    	.text
    	.align  2
    .globl   _main
    	.def	_main;	.scl	2;	.type	32;	.endef
    _main:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$8, %esp
    	andl	$-16, %esp
    	movl	$0, %eax
    	addl	$15, %eax
    	addl	$15, %eax
    	shrl	$4, %eax
    	sall	$4, %eax
    	movl	%eax, -8(%ebp)
    	movl	-8(%ebp), %eax
    	call	__alloca
    	call	___main
    	subl	$12, %esp
    	leal	-4(%ebp), %eax
    	pushl	%eax
    	call	__ZN3ABCC1Ev
    	addl	$16, %esp
    	movl	$10, -4(%ebp) // Create a local variable which is actually "abc.a"
    	subl	$12, %esp
    	leal	-4(%ebp), %eax
    	pushl	%eax
    	call	__ZN3ABC7Method1Ev // call "abc.Method1()" This includes the method in the assembly
    	addl	$16, %esp
    	movl	$0, %eax
    	leave
    	ret
    	.section 	.text  $_ZN3ABC7Method1Ev,"x"
    	.linkonce discard
    	.align  2
    .globl __ZN3ABC7Method1Ev
    	.def	__ZN3ABC7Method1Ev;	.scl	2;	.type	32;	.endef
    __ZN3ABC7Method1Ev:
    	pushl	%ebp
    	movl	%esp, %ebp
    	subl	$4, %esp
    	movl	$10, -4(%ebp)
    	leave
    	ret
    	.section 	.text   $_ZN3ABCC1Ev,"x"
    	.linkonce  discard
    	.align   2
    .globl __ZN3ABCC1Ev
    	.def 	__ZN3ABCC1Ev;	.scl	2;	.type	32;	.endef
    __ZN3ABCC1Ev:
    	pushl	%ebp
    	movl	%esp, %ebp
    	leave
    	ret
    
    您可能会注意到汇编代码中没有类定义。类定义仅存储在 C++ 编译器的符号表中。您可能会看到变量“a”在 main 函数中使用,但没有 ABC 类的变量。变量“a”只是存储在一个局部变量中(请参阅注释)。方法存储为“__ZN3ABC7Method1Ev”,因为我们从 main 函数调用了它;您可能会惊讶地发现 Method2 没有定义。这是因为我们没有使用它。编译器决定是否包含字段或函数仅当它被使用时。很明显,“ABC”类存储在编译器的符号表中,然后如果使用了某个字段,编译器就会将其包含在汇编中。这里我们使用了 Method1,因此编译器将 Method 包含在汇编中,名称为“__ZN3ABC7Method1Ev”。当我们调用函数时,您可能会注意到语句“call __ZN3ABC7Method1Ev”。这实际上是 OOP 原生编译器将代码编译成本地代码的方法。  
  • 类转换 - C# 的方式

    上面我们讨论了原生 C++ 编译器如何编译类。以下是 C# 编译器编译类的方式。考虑一个名为“ABC”的类,它有一个变量“a”,被编译成 .NET 可执行文件,然后是反汇编。命名空间名称是“ClassConversion”,这是一个简单的控制台程序。

    演示程序

    提供的演示程序显示了 Python 和 C++ 之间的简单接口。实际上,它实现了一个简单的 Python 脚本系统。
    它定义了两个 Python 方法,名为“DisplayDayNames()”,它显示一周的七天,以及“Distance(x1,y1,x2,y2)”,它使用简单的数学公式计算两个坐标之间的距离。

    PyRun_SimpleString("import DemoModule");
    PyRun_SimpleString("from DemoModule import *");
    PyRun_SimpleString("print Distance(2,3,4,5);"); // Call the method "Distance" from python
    PyRun_SimpleString("DisplayDayNames();"); 

    上面的 4 行使用嵌入式解释器逐行执行 Python 代码。 

    结论

    本文通过描述优缺点,并不主张只使用编译型语言或只使用解释型语言。也可以完全同时使用两者。但您必须非常清楚何时使用哪种语言。例如,如果您选择 Python 来开发应用程序,您仍然可以使用 C++ 来加速代码中需要速度的关键部分。与其使用 Python 发行版附带的库,不如使用Boost.Python 库来无缝地协同工作。下载完整的Boost 发行版,它在所有类型的开发中都将非常有用。

    更多信息

    参考文献 

    《D 编程语言》作者:Andrei Alexandrescu - 购买本书

    《编译原理、技术和工具》作者:Alfred V. Aho、Monica S. Lam、Ravi Sethi 和 Jeffrey D. Ullman - 购买本书 

    在线参考 

    解释型语言 - 转到页面 

    编译型语言 - 转到页面 

    托管代码 - 转到页面 

    网站  

    Boost 库站点 - www.boost.org 

    Python 网站 - www.python.org 

    D 编程网站 - www.dprogramming.com

    历史  

    © . All rights reserved.