深入理解PHP原理之PHP脚本执行原理(2)
发布日期:2021-10-05 13:28:26 浏览次数:1 分类:技术文章

本文共 3709 字,大约阅读时间需要 12 分钟。

  在上一篇《》文章中已经介绍了PHP的语法分析、词法分析和Opcodes的原理,下面主要通过一个具体的示例,来看看ZE是怎样对PHP脚本进行语法分析和词法分析,并将其编译为Opcodes,下面让我们回顾一下PHP脚本执行的基本流程:

  1. Zend Engine(ZE)调用词法分析器(Lex生成的,源码路径:php/Zend/zend_language_sanner.l), 将我们要执行的PHP源文件去掉空格和注释后,分割成一个个的token;
  2. ZE会将得到的token forward给语法分析器(yacc生成, 源码路径:php/Zend/zend_language_parser.y),将Tokens转换成简单而有意义的表达式;
  3. ZE会将转换后的表达式,编译为一个个opcode,opcode一般会以op_array的形式存在,它是PHP执行的中间语言。
  4. ZE调用zend_executor来执行op_array,输出结果。
  下面通过具体示例来讲解上述过程,还是以经典的“hello world”为示例,如下:

$code =<<

  函数token_get_all()可以将一段PHP代码 Scanning成tokens,分割后的结果如下:

array(19) {  [0]=>  array(3) {    [0]=>    int(367)    [1]=>    string(6) "
int(1) } [1]=> array(3) { [0]=> int(370) [1]=> string(1) " " [2]=> int(2) } [2]=> string(1) "=" [3]=> array(3) { [0]=> int(370) [1]=> string(1) " " [2]=> int(2) } [4]=> array(3) { [0]=> int(315) [1]=> string(13) ""Hello World"" [2]=> int(2) } [5]=> string(1) ";" [6]=> array(3) { [0]=> int(370) [1]=> string(2) " " [2]=> int(2) } [7]=> string(1) "=" [8]=> array(3) { [0]=> int(370) [1]=> string(1) " " [2]=> int(3) } [9]=> array(3) { [0]=> int(305) [1]=> string(1) "1" [2]=> int(3) } [10]=> array(3) { [0]=> int(370) [1]=> string(1) " " [2]=> int(3) } [11]=> string(1) "+" [12]=> array(3) { [0]=> int(370) [1]=> string(1) " " [2]=> int(3) } [13]=> array(3) { [0]=> int(305) [1]=> string(1) "1" [2]=> int(3) } [14]=> string(1) ";" [15]=> array(3) { [0]=> int(370) [1]=> string(1) "" [2]=> int(3) } [16]=> array(3) { [0]=> int(316) [1]=> string(4) "echo" [2]=> int(4) } [17]=> array(3) { [0]=> int(370) [1]=> string(1) " " [2]=> int(4) } [18]=> string(1) ";"}

  通过上面的返回结果,我们发现第一步“词法分析”将php脚本分割成数组array,该数组中包括php脚本中的空格、字符、操作数、语句等。在第二步“语法分析”中,数组array中的操作符、语句和标签等会根据文件php/Zend/zend_language_parser.y转换成tokens(下面是该文件部分源码,包含echo、while等关键字的tokens转换),但是字符串,字符,空格等不会,转换后的内容包含两个部分的Array:Token ID (也就是在Zend内部的改Token的对应码)和源码中的原来的内容。

%token T_IF        "if (T_IF)"%token T_ELSEIF    "elseif (T_ELSEIF)"%token T_ELSE      "else (T_ELSE)"%token T_ENDIF     "endif (T_ENDIF)"%token T_ECHO      "echo (T_ECHO)"%token T_DO        "do (T_DO)"%token T_WHILE     "while (T_WHILE)"

  然后就是Parsing阶段,Parsing首先会丢弃Tokens Array中的多余的空格,然后将剩余的Tokens转换成一个个简单的表达式,Parsing后的结果大致如下:

* ZEND_ECHO     'Hello World'  // echo a constant string* ZEND_ADD      ~0 1 1         // add two numbers together* ZEND_ASSIGN   !0 ~0          // store the result of the prior expression to a variable* ZEND_ECHO     !0             // echo a variable

  第三步就是Compilation阶段,它会把Parsing后的每个表达式编译成一个个zend_op,让我们回顾一下zend_op的结构:

struct _zend_op {    opcode_handler_t handler;   // 执行该opcode时调用的处理函数    znode result;               // 执行完后的返回结果    znode op1;                  // 操作数1    znode op2;                  // 操作数2    ulong extended_value;       // 执行时可能还需要用到的其它信息    uint lineno;    zend_uchar opcode;          // opcode代码};

  第四步就是将Compilation阶段生成的zend_op整合成op_array,然后就可以通过zend_executor执行,整个过程就结束了,你可能会问了,我们的$a去那里了?下面就直接引用鸟哥的解释,因为最后一个地方我也没看懂 /(ㄒoㄒ)/~~

  这个要介绍操作数了,每个操作数都是由以下俩个部分组成:

a)op_type : 为IS_CONST, IS_TMP_VAR, IS_VAR, IS_UNUSED, or IS_CVb)u,一个联合体,根据op_type的不同,分别用不同的类型保存了这个操作数的值(const)或者左值(var)

  对于var来说,每个var也不一样:

  IS_TMP_VAR,顾名思义,这个是一个临时变量,保存一些op_array的结果,以便接下来的op_array使用,这种操作数的u保存着一个指向变量表的一个句柄(整数),这种操作数一般用~开头,比如~0表示变量表的0号未知的临时变量;
  IS_VAR 这种就是我们一般意义上的变量了,他们以$开头表示;
  IS_CV 表示ZE2.1/PHP5.1以后的编译器使用的一种cache机制,这种变量保存着被它引用的变量的地址,当一个变量第一次被引用的时候,就会被CV起来,以后对这个变量的引用就不需要再次去查找active符号表了,CV变量以!开头表示。
  这么看来,我们的a被优化成!0了。(这里没看懂,为什么变成!0了???)
  
参考:
参考:

转载地址:https://blog.csdn.net/lml200701158/article/details/52295747 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:深入理解PHP原理之常量
下一篇:深入理解PHP原理之PHP脚本执行原理(1)

发表评论

最新留言

留言是一种美德,欢迎回访!
[***.207.175.100]2024年03月15日 08时08分44秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

mqtt session保持 订阅消息_为什么说MQTT是为物联网量身定做的通讯协议? 2019-04-21
小程序制作预算_小程序开发制作价格 2019-04-21
偏移 柱状图_柱状图写入表格 2019-04-21
orcale的mysql语言_将oracle语句转化为mysql的语句 2019-04-21
怎么查看mysql内网_Mysql查询语句 2019-04-21
将任课老师改为张小明mysql_Mysql 练习题 2019-04-21
mysql的json函数与实例_mysql(5.6及以下)解析json的方法实例详解 2019-04-21
mysql数据库攻击与防御pdf_SQL注入攻击与防御 中文PDF清晰扫描版(38.6M) 2019-04-21
win+d没反应_黑科技!2.5D风格场景插画立体图案一键生成PS插件 2019-04-21
kafka python客户端连接风暴_Apache 风暴 kafka spout 只读取主题分区的一半 2019-04-21
python 硬编码_试试 python-dotenv,避免敏感信息被硬编码到代码中 2019-04-21
python循环 文件名_Python 与 Stata 配合批量转换数据 2019-04-21
wincc连接mysql数据库说明_wincc 数据库的连接方法 2019-04-21
matlab std函数 omitnan_MATLAB之使用GUI处理语音信号(三)时域分析及卷积运算 2019-04-21
mysql master线程_MySQL Replication 线程(理解详细过程) 2019-04-21
mysql 查询多级类别_SQL处理多级分类,查询结果呈树形结构 2019-04-21
mysql怎么查询上一条记录_MySQL中查询已知记录的上一条和下一条记录 2019-04-21
python爬虫学习笔记_python网络爬虫学习笔记(1) 2019-04-21
区块链需要用到mysql吗_什么条件下才需要区块链 2019-04-21
mysql5.7 archive安装_Mysql5.7.19winx64ZIPArchive安装及使用过程问题小结 2019-04-21