顺风车运营研发团队李智发表于程序生活公众号我们常用的高级语言有很多,比较有名的有CC++、Python、PHP,Go,Pascal等。根据运行方式的不同,这些语言大致可以分为两种:编译型语言和解释型语言。其中,编译型语言包括CC++、Pascal、Go等。这里所说的编译是指在应用程序源程序执行前,将程序源代码“翻译”成汇编语言,再进一步编译成目标文件根据软硬件环境。一般我们把完成编译工作的工具称为编译器。当程序运行时,解释语言被“翻译”成机器语言。但是,“翻译”只进行一次,执行效率低。解释器的工作是负责用解释性语言“翻译”源代码的程序。让我们更详细地讨论编译型和解释型语言是如何工作的。1、编译语言和解释语言我们知道,对于一段C语言代码,需要经过预编译、编译、汇编和链接,才能成为可执行的二进制文件。以hello.c为例:#includeintmain(){printf("helloworld");return1;}对于这段C代码,main是程序入口函数,它的作用是将字符串“helloworld”打印到屏幕上。编译执行过程如图1所示。图1编译语言执行示意图第1步:C语言代码预处理(如依赖处理、宏替换等)。如上面的代码示例,#inlcude将在预处理阶段被替换。第二步:编译。编译器会将C语言翻译成汇编语言程序,一段C语言通常意味着多段汇编代码。同时,编译器会对程序进行优化,生成目标汇编程序。第三步:编译后的汇编语言再通过汇编器编译成目标程序hello.o。第4步:链接。程序中往往包含一些共享对象文件,如示例程序中的printf()函数,位于静态库中,需要通过链接器(如Uinxconnectorld)进行链接。对于以C语言为代表的编译型语言,代码更新必须经过以上步骤:我们区分编译型语言和解释型语言,主要是根据源代码编译成目标平台CPU指令的时机。对于编译型语言,编译后的结果已经是当前CPU系统的一条指令;对于解释型语言,需要先编译成中间代码,再通过解释型语言的特定虚拟机翻译成特定CPU系统的指令执行。解释性语言是在运行时被翻译成目标平台的指令。人们常说解释型语言“慢”,主要是这里慢。在PHP7中,会先对源码进行词法分析,将源码分割成多个字符串单元,分割后的字符串称为Token。但是单个的Token并不能表达完整的语义,需要经过句法分析阶段,将Token转化为抽象语法树(AbstractSyntaxTrees,简称AST)。之后,将抽象语法树转化为机器指令执行。在PHP中,这些指令被称为操作码(后面会详细解释操作码,这里的读者可以看成是CPU指令)。直到生成AST这一步,编译型语言和解释型语言需要经历的过程是相似的。差异始于抽象语法树之后。图2是PHP(如无特殊说明,本章所指的PHP均为PHP7版本)代码执行的简化步骤,最后一步左侧分支为编译语言的过程。图2以PHP为例执行解释型语言示意图第一步:分析源代码,通过词法分析得到Token;第二步:基于语法分析器生成抽象语法树(AST);Step3:ConvertabstractsyntaxtreetoOpcodes(操作码指令集),PHP解释执行Opcodes。接下来,我们将在基本步骤的基础上,提炼PHP语言的执行原理,尝试建立更清晰的认知。2.PHP7执行原理概述首先,我们补充一下上面提到的PHP7程序执行过程的描述,请参考图3。图3PHP7语言编写的程序执行过程图Step1:Lexical分析将PHP代码转换为有意义的标识Token。这一步的词法分析器是使用Re2c实现的。Step2:语法分析从Token和符合语法规则的代码生成抽象语法树。解析器是基于Bison实现的。文法分析使用Backus-NaurForm(BNF)表达语法规则,Bison借助状态机、状态转换表,以及一系列的入栈出栈操作生成抽象语法树。第三步:上一步的抽象语法树生成相应的操作码,由虚拟机执行。opcode是PHP7定义的一组指令标识,指令对应相应的handler(处理函数)。当虚拟机调用opcode时,会找到opcode背后的处理函数,进行真正的处理。以我们常见的echo语句为例,其对应的操作码为ZEND_ECHO。注:为了便于理解词法分析和句法分析过程,将两者分开说明。但实际上,为了效率,两个进程并不是完全独立的。下面,我们通过一段示例代码来初步了解PHP7的运行。示例代码如下:数组([0]=>379[1]=>1)[1]=>数组([0]=>328[1]=>回显[2]=>1)[2]=>数组([0]=>382[1]=>[2]=>1)[3]=>数组([0]=>323[1]=>“你好世界”[2]=>1)[4]=>;)上面输出中,二维数组的每个成员数组的第一个值为Token对应的枚举值;第二个值为Token对应的原始字符串内容;第三个值对应代码的行号。可以看出,词法分析器对