摘要:在中,源代码首先将进行词法分析,将源代码切割为多个字符串单元,分割后的字符串称之为。图以为例解释型语言的执行示意图第步源码通过词法分析得到第步基于语法分析器生成抽象语法树第步抽象语法树转换为指令集合,解释执行。
顺风车运营研发团队 李志 发表在程序人生 公众号
我们常用的高级语言有很多种,比较出名的有CC++、Python、 PHP、Go、Pascal等。而这些语言根据运行的方式不同,大体分为两种:编译型语言和解释型语言。
其中,编译型语言包括CC++、Pascal、Go等。这里说的编译是指在应用源程序执行之前,就将程序源代码“翻译”成汇编语言,然后进一步根据软硬件环境编译成目标文件。一般我们称完成编译工作的工具叫编译器。而解释型语言,在程序运行时才被“翻译”为机器语言。但是执行一次“翻译”一次,所以执行效率较低。解释器的工作就是解释性语言中,负责“翻译”源代码的程序。
下面我们更详细地讨论一下编译型语言和解释性语言的运行方式。
一、编译型语言与解释型语言
我们知道,对于一段C语言代码,需要经过预编译、编译、汇编和链接,才能成为可执行的二进制文件。以hello.c为例:
#includeint main(){ printf("hello world"); return 1; }
对于这段C代码,main是程序入口函数,实现的功能是打印字符串“hello world” 到屏幕上。编译和执行过程如图1所示。
图1 编译型语言的执行示意图
第1步:C语言代码预处理(比如依赖处理、宏替换等)。如以上代码示例,#inlcude
第2步:编译。编译器会把C语言翻译成汇编语言程序,一条C语言通常便以为多条汇编代码。同时编译器会对程序进行优化,生成目标汇编程序。
第3步:编译得到的汇编语言通过汇编器再汇编成目标程序hello.o。
第4步:链接。程序中往往包含一些共享目标文件,如示例程序中的printf()函数,位于静态库,需要经过链接器(如Uinx连接器ld)进行链接。
以C语言为代表的编译型语言,代码发生更新都要经过以上步骤:
我们区别编译型语言与解释型语言,主要立足于源代码被编译成目标平台CPU指令的时机。对于编译型语言,编译结果已经是针对当前CPU体系的指令;而解释型语言,需要先编译成中间代码,再经由该解释型语言的特定虚拟机,翻译成特定CPU体系的指令被执行。解释型语言是在运行过程中,翻译为目标平台的指令。常说解释型语言“慢”,主要也是慢在这里。
在PHP7中,源代码首先将进行词法分析,将源代码切割为多个字符串单元,分割后的字符串称之为Token。而一个个独立的Token无法表达完整语义,需经过语法分析阶段,将Token转换为抽象语法树(简称AST)。之后,抽象语法树被转换为机器指令执行。在PHP中,这些指令称为opcode(后文会对opcode做更详细的解释,此处读者可以看待为CPU指令)。
到AST的生成这一步,编译型语言与解释型语言所需经历的过程相似。从抽象语法树之后开始产生差异。
图2是PHP(如无特殊说明,本章提到的PHP均为PHP7版本)代码被执行的简化步骤,其中最后一步的左侧分支,是编译型语言的过程。
图2 以PHP为例解释型语言的执行示意图
第1步:源码通过词法分析得到Token;
第2步:基于语法分析器生成抽象语法树(AST);
第3步:抽象语法树转换为Opcodes(opcode指令集合),PHP解释执行Opcodes。
接下来我们在基本步骤的基础上,细化PHP语言的执行原理,试图更清晰地建立认知。
二、PHP7的执行原理概述
首先我们补充说明下前文提到的PHP7程序执行过程,请参见图3。
图3 PHP7语言编写的程序的执行过程图
第1步:词法分析将PHP代码转换为有意义的标识Token。该步骤的词法分析器使用Re2c实现的。
第2步:语法分析将Token和符合文法规则的代码生成抽象语法树。语法分析器基于Bison实现。语法分析使用了巴科斯范式(BNF)来表达文法规则,Bison借助状态机、状态转移表和压栈、出栈等一系列操作,生成抽象语法树。
第3步:上步的抽象语法树生成对应的opcode,被虚拟机执行。opcode是PHP7定义的一组指令标识,指令对应着相应的handler(处理函数)。当虚拟机调用opcode,会找到opcode背后的处理函数,执行真正的处理。以我们常见的echo语句为例,其对应的opcode便是ZEND_ECHO。
注意:这里为了便于理解词法分析和语法分析过程,将两者分开描述。但实际情况,出于效率考虑,两个过程并非完全独立。
下面,我们通过一段示例代码,来建立PHP7运转的初步理解。
示例代码如下:
从图3可知,这段代码首先会被切割为Token。
1. Token
Token是PHP代码被切割成的有意义的标识。本书介绍的PHP7版本中有137 种Token,在zend_language_parser.h文件中做了定义:
/* Tokens. */ #define END 0 #define T_INCLUDE 258 #define T_INCLUDE_ONCE 259 … #define T_ERROR 392更多Token的含义,感兴趣的读者可以参考《PHP 7底层设计与源码实现》附录。
PHP提供了token_get_all()函数来获取PHP代码被切割后的Token,可以在深入源码学习前,粗略查看PHP代码被切割后的Token。如下代码片段:
/home/vagrant/php7/bin/php –r "print_r(Token_get_all("输出结果为:
Array ( [0] => Array ( [0] => 379 [1] => 1 ) [1] => Array ( [0] => 328 [1] => echo [2] => 1 ) [2] => Array ( [0] => 382 [1] => [2] => 1 ) [3] => Array ( [0] => 323 [1] => "hello world" [2] => 1 ) [4] => ; )上文输出中,二维数组的每个成员数组第一个值为Token对应的枚举值;第二个值为Token对应的原始字符串内容;第三个值为代码对应的行号。可以看出,词法解析器将
1)文本“
#dfine T_OPEN_TAG 379不难理解,它是PHP代码的起始tag,也就是
2)echo对应的Token是T_ECHO:
#define T_ECHO 3283)源码中的空格,对应的Token叫T_WHITESPACE,值为382:
#define T_WHITESPACE 3824)字符串“hello world”对应的Token值为323:
#define T_CONSTANT_ENCAPSED_STRING 323可见,Token就是一个个的“词块”,但是多带带存在的词块不能表达完整的语义,还需要借助规则进行组织串联。语法分析器就是这个组织者。它会检查语法、匹配Token,对Token进行关联。
PHP7中,组织串联的产物就是抽象语法树(Abstract Syntax Tree,AST)。
2. AST
AST是PHP7版本新特性。在这之前的版本,PHP代码的执行过程中没有生成AST这一步。PHP7对抽象语法树的支持,实现了PHP编译器和解释器解耦,有效提升了可维护性。
顾名思义,抽象语法树具有树状结构。AST的节点分为多种类型,对应着不同的PHP语法。在当前章节,我们可以认为节点类型是对语法规则的抽象,例如赋值语句,生成的抽象语法树节点为ZEND_AST_ASSIGN。而赋值语句的左右操作数,又将作为ZEND_AST_ASSIGN类型节点的孩子。通过这样的节点关系,构建出抽象语法树。
如果读者希望一睹为快,可以直接跳到本书第13章函数的实现,其中图片描绘了一段简单的PHP代码生成的抽象语法树。
在这里,我们推荐读者了解下PhpParser工具,可以用它来查看PHP代码生成的AST。
注意:PHP-Parser是PHP7内核作者之一nikic编写的将PHP源码生成AST的工具。源码见https://github.com/nikic/PHP-...
3. Opcodes
AST扮演了源码到中间代码的临时存储介质的角色,还需要将其转换为opcode,才能被引擎直接执行。Opcode只是单条指令,Opcodes是opcode的集合形式,是PHP执行过程中的中间代码,类似Java中的字节码。生成之后由虚拟机执行。
我们知道,PHP工程优化措施中有个比较常见的“开启Opcache”,指的就是这里的Opcodes的缓存(Opcodes Cache)。通过省去从源码到opcode的阶段,引擎可以直接执行缓存的opcode,以此提升性能。
借助vld插件,可以直观地看到一段PHP代码生成的opcode:
php -dvld.active=1 hello.php 经过过滤整理,对应的opcode为: line op 1 ECHO 2 RETURN其实在源码实现中,上述代码生成的opcode及handler为:
ZEND_ECHO // handler: ZEND_ECHO_SPEC_CONST_HANDLER ZEND_RETURN // handler: ZEND_RETURN_SPEC_CONST_HANDLER可见,ZEND_ECHO对应的handler是ZEND_ECHO_SPEC_CONST_HANDLER。此handler的实现的功能便是预期的“hello world”语句的输出。
本书的PHP版本中,内核在zend_vm_opcodes.h中定义了186种Opcodes,也可以参考《PHP 7底层设计与源码实现》附录部分。
在平时的业务开发中,了解一些 PHP的底层实现,尤其是语法机制的实现,对性能提升、故障排除非常有好处。希望这篇浅文,可以帮助读者在使用PHP7的同时,了解到编写的PHP代码如何被编译和执行。
(扫上方二维码7.9折购买)
推荐理由:
滴滴出行专家联合撰写,PHP领域大咖夏绪宏、韩天峰、王晶、谢华亮(黑夜路人)、伍星联袂推荐
全面吃透PHP内核架构、核心实现与内存管理、词法与句法解析、Zend 虚拟机、函数及关键扩展等设计细节与源码实现
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/28985.html
摘要:我们修改上面代码,再来看下返回值类型限制的情况运行结果这段代码我们额外声明了返回值的类型为型。对函数返回值的声明做了扩充,可以定义其返回值为,无论是否开启严格模式,只要函数中有以外的其他语句都会报错。 顺风车运营研发团队 王坤 发表至21CTO公众号(https://mp.weixin.qq.com/s/ph...) showImg(https://segmentfault.c...
摘要:中词法语法分析,生成抽象语法树,然后编译成及被执行均由虚拟机完成。通常情况下这部分是可选部分,主要为便于程序的读写方便而使用。指令虚拟机的指令称为,每条指令对应一个。 作者 陈雷编程语言的虚拟机是一种可以运行中间语言的程序。中间语言是抽象出的指令集,由原生语言编译而成,作为虚拟机执行阶段的输入。很多语言都实现了自己的虚拟机,比如Java、C#和Lua。PHP语言也有自己的虚拟机,称为Z...
摘要:重点分析其中宏,返回表的,后面用来作为索引查询的依据。中资源的解析比中解析简单快捷很多,得益于其结构的改变。先从逆向通过其在中索引层层关联,找到该类资源的释放回调函数,然后对该资源执行释放回调函数。 PHP 扩展开发的文章,我均已更新至《TIPI》本篇承接上篇 PHP7 使用资源包裹第三方扩展的实现 PHP7 使用资源包裹第三方扩展原理分析 注册资源类型源码 [c] ZEND_API ...
阅读 761·2019-08-29 12:49
阅读 3549·2019-08-29 11:32
阅读 3433·2019-08-26 10:43
阅读 2400·2019-08-23 16:53
阅读 2046·2019-08-23 15:56
阅读 1694·2019-08-23 12:03
阅读 2765·2019-08-23 11:25
阅读 2083·2019-08-22 15:11