就在前两个月,王垠的博客网站关闭了一段时间,并在重新开启时删除了所有旧文章,理由可以从他新发的版权声明中见到。对于王垠对待某些网站、某些网民以及他自己过往文章的态度,我不置可否。但至少一点让我感到惋惜的是我失去了一个非常好的学习机会。所幸他的多数博文我在网站关闭前都大致看过一遍,虽说被有一一细细品读也算是一种安慰了吧。
我写这篇文章以及接下来的一系列文章的目的是希望向国内广大程序员们进行一些编程语言相关的“科普”。说是科普是因为我意识到,对于绝大多数国内的程序员来说,他们对于编程语言的认识,随着项目经验的增加而增长得非常有限。很多经历过许多大项目、自称精通许多编程语言的程序员们其实对他们手边天天使用的语言并没有一个甚至基本的了解。他们依赖所谓的“编程范式”、信奉经验主义、用夜以继日的加班、过劳来试图修复他们代码中的众多问题,却没有思考过造成这些问题的基本问题,造成问题的问题,就如同描述数据的数据——元数据一样,这些“元问题”很多时候都能本质上地改变程序员的工作强度与工作方式。一旦解决了“元问题”,很多现有的问题将不复存在。
“科普”的另一层含义是在接下来的文章里我只管介绍,而不会指导你去如何学习,因此你大可抱着看热闹的心情去看我接下来的文章,但如果你想要系统地学习它,Sorry,我没有这个能力,你可以阅读相关的书籍,我的文章可能可以帮助你理解这些问题,但真的要深入了解、掌握编程语言的本质的话,还是需要你自己的努力。
要了解编程语言的本质,最直接的办法,就是去了解编程语言的解释器的运行机制。很多人会问,我知道PHP、Python是解释性语言,可以用解释器去运行它们的代码,但如果是C、C++、Objective-C这样的编译性语言呢?你的科普是不是就不通用了?
关于这一点,首先我想先阐明一个事实,没有一门编程语言可以被称为“解释性语言”或者“编译性语言”,编程语言仅仅是程序员和机器之间进行交流的一种规范的、有条理的、形式化的媒介。就好比自然语言是用于人与人之间交流的媒介一样。所谓的PHP、Python是解释性语言、C、C++是编译性语言,只不过是因为它们最主流的实现方式分别是边解释边执行(解释性,其实这一说法并不严密)、以及先编译后执行(编译性)。你大可以写出一个PHP的编译器或者C语言的解释器(其实这两种实现都存在),只不过它们不是最主流的实现方式而已。
其次,不管是用gcc、clang去编译C语言,还是用Zend引擎去解释PHP,它们在前期的工作都是类似的,都需要逐字逐句地解析程序员写的代码,并将其转换为一种内部的数据结构。所不同的是,在解析完代码之后,编译器会将这一种内部数据结构再次转换成新的一种代码,比如gcc的编译器将C代码转换为汇编代码,因此,编译的过程本质上就是一种转换的过程;而解释器则根据不同的数据结构来指挥机器做不同的工作,直到解释结束。
在实际的实现中,基于效率问题,多数语言的解释器都并不直接解释执行代码(包括PHP和Python),而是先将它们转换为一种便于机器理解、执行的中间代码,这本质上也是一种编译,再去执行。其此,实际上主流的编程语言实现中并不存在纯粹的“解释器”,只有先编译,稍后执行的解释器(也就是我们常说的编译器);以前先编译、之后立即执行的解释器。
其实,学习解释器的构造对一个程序员的成长来说是很有好处的,除了之前说的,你可以通过学习解释器来认识编程语言的本质之外,你还可以对你自己现在的或者将来的项目有着更好的掌控。这是因为随着现代软件的复杂性不断提高,很多程序与解释器已经越来越接近了,用户与程序交互的过程,就如同代码与解释器交互的过程;程序里的业务逻辑需要一系列例如生命周期、迭代、状态记录、中断流程与恢复之类的行为的实现,在解释器内部也有着相应的体现。有时候,你的工程虽然本身并不是一个解释器,却已经在无意识间实现了解释器的某些功能。