在学习go的时间接触到,go可以支持静态编译,然后我突然意识到,尽管我一直知道c/c++是静态语言,python是动态语言,但两种语言各自具备的特性,我却一直不是很清晰了解,因此在这里对其进行记录。
源代码的执行方式
在讨论两种语言之前,需要先了解计算机执行源程序的两种方式,分别是编译和解释
编译
编译是把程序员所写的可读源代码一次性转为不可读的二进制目标代码的过程,主要是编译器负责这一过程,主要包含预编译、编译、汇编、链接四个流程
-
预编译
预编译主要对宏等信息进行提前处理,包括展开头文件、宏替换、去注释、条件编译
-
编译
该过程将代码转为汇编代码,不同架构下的汇编代码可能不同
在编译过程中,编译器在每个文件中保存一个函数地址符表,该表中存储着当前文件内包含的各个函数的地址,调用函数的代码会被编译成一条call指令,call指令后面跟的是jmp指令的汇编代码地址,而jmp指令后面跟的才是“被调用的函数编译成汇编代码后的第一条指令”的地址,但是给call指令后面补充上地址的工作是在链接的时候做的事情。
-
汇编
将汇编代码转为二进制机器码
-
链接
将产生的多个二进制文件与对应的库链接起来生成可执行文件。
编译器做的一个重要的事情是将每个文件中call指令后面的地址补充上;方式是从当前文件的函数地址符表中开始找,如果没有,继续向别的文件的函数地址符表中找。
解释
将源代码逐条转换为目标代码同时逐条执行的过程,每次程序运行时随时翻译随时执行(类似于同声传译),该过程主要通过解释器来完成。
解释程序由一个总控程序和若干个子程序组成
- 总控程序执行初始准备工作,置工作初态
- 从源程序中取一个语句,并进行语法检查。如果语法有错,则输出错误信息;否则,根据所确定的语句类型转去执行相应的执行子程序
- 返回后检查解释工作是否完成,如果未完成,则继续解释下一语句;否则,进行必要的善后处理工作。
也就是说,解释程序一般分为解释模块和运行模块两个模块
解释模块按源程序动态执行顺序逐个输入语句,并对单个语句进行分析和解释,包括语法和语义的正确性检验、生成等价的中间代码或机器语言代码,以及错误信息提供等处理
而运行模块则是运行语句翻译后的中间代码,并生成结果
与编译生成的结果代码不同的是,解释模块可能生成的是机器代码,通常一个语句生成多条机器指令的代码段,运行模块负责控制这段代码的执行并处理中间结果。但同时,解释模块也可能生成非机器代码的等价中间代码(例如Python的字节码)
相比于汇编,它不直接生成目标代码,而是一句句边解释边运行。可分为两遍扫描完成
- 读入源程序,由扫描器和部分语法分析器完成,其主要工作是:
- 对源程序进行词法检查和部分语法检查;
- 把源程序字符串转化为内部形式的源程序(单词符号串);
- 建立各种符号表,为解释执行阶段做准备。
- 解释执行,由解释执行程序完成,其主要工作是:
- 使用第一阶段形成的符号表对内部源程序逐条解释执行;
- 在解释执行过程中,进行全部语法检查
对比汇编,解释更加灵活,交互性好,但同时它的运行效率也会更低
静态语言和动态语言
讨论完了两种程序执行方式,其实我们已经讨论了静态语言和动态语言了。
但语言其实与程序执行方式并不完全等同,严格来说,静态语言是在编译时变量的数据类型即可确定的语言,而动态语言是在运行时确定数据类型的语言。
静态语言的变量在编译时就已经确定,而运行时不能修改,动态语言在运行的时候确定变量,且可以修改。
举个简单的栗子,java是典型的静态语言,代码一旦运行编译成class文件后,就无法对内部的代码和变量再进行修改了,数据类型在编译一开始就已经完全确定。
那么可以说Java是严格的通过编译进行执行吗?我个人觉得并不可以,因为java是以虚拟机的形式运行的,而在编译阶段,class文件包含并不是cpu直接运行的机器码,而是中间字节码,字节码会在虚拟机上进行解释执行,因此java应该是一种先编译再解释的静态语言。
所以尽管二者具有相似的概念,但我们不能等同来看。编译就是预定的家常菜餐厅,全部做好后给你上菜;解释则是吃火锅,烫一个吃一个。静态语言是菜单定下后无法修改,动态语言则是到店后开吃后决定吃的什么。java则可以理解为固定菜品的火锅餐厅,尽管是做一个吃一个,但吃什么已经固定无法修改,因此它是静态语言。
参考文献