PHP的操作码全称是:Operation Code(OpCode) 。在写之前也在网上查了相关需要的资料,发现有些人理解的不是很正确。比如:将操作码理解为“底层代码”,这个是不能这样理解的。首先来看什么是“底层代码”。
SCL:时钟线
SDA:数据线
在高电平的时候数据不允许发生改变,在低电平的时候数据可以发送改变。这里的时钟控制就是以晶振来做时钟源的。晶振是什么?嗯,发现越扯越远。回到上面的内容,为什么说Opcode不是底层代码。是因为Opcode代码最终的执行者不是CPU,而是解释器。有解释器的语言基本上都是脚本语言,这里的PHP就是一个典型的脚本语言,因为其中的Zend引擎就是专门解析PHP代码并且执行的。但是执行的前提是把代码编译为操作码(Opcode),Zend才能执行,所以Opcode在PHP里也叫中间代码。这里是因为编译的一个过程。代码的编译过程大致如下:分割Token、词法分析、语法分析、编译、执行。这里的Token就是一个不可分割的最小单元。嗯。感觉又要说远了。总的来说在语法分析阶段产生了中间码(Opcode)。实际不要这个中间码感觉也行。但是要考虑一种情况。那就是PHP毕竟是Web语言,如果没有中间码会出现执行率下降问题。而有了中间码,可以将中间码缓存,下次直接执行。这也是OpCache的原因,很多的PHP代码加密工具就是对Opcode进行了二次处理。下面来分析一下php的中间码(OpCode),这里以php7.3.4静态分析举例。把下面代码编译成Opcode。
<?php echo 123;
Opcode
看这一行:2 0 E > EXT_STMI "123" ,有几个关键的点需要注意。
2:PHP源码的行号
0:Opcode的行号
EXT_STMT:中间码的操作标识
123:中间码的操作值
这里要说一下Opcode的数据结构如下
struct _zend_op_array { /* Common elements */ zend_uchar type; zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */ uint32_t fn_flags; zend_string *function_name; zend_class_entry *scope; zend_function *prototype; uint32_t num_args; uint32_t required_num_args; zend_arg_info *arg_info; /* END of common elements */ int cache_size; /* number of run_time_cache_slots * sizeof(void*) */ int last_var; /* number of CV variables */ uint32_t T; /* number of temporary variables */ uint32_t last; /* number of opcodes */ zend_op *opcodes; void **run_time_cache; HashTable *static_variables; zend_string **vars; /* names of CV variables */ uint32_t *refcount; int last_live_range; int last_try_catch; zend_live_range *live_range; zend_try_catch_element *try_catch_array; zend_string *filename; uint32_t line_start; uint32_t line_end; zend_string *doc_comment; int last_literal; zval *literals; void *reserved[ZEND_MAX_RESERVED_RESOURCES];};
然后看一下万年不变的问题:For和ForEach到底谁快
<?php $arr=array(1,2,3); foreach ($arr as $value) echo $value;
<?php $arr=array(1,2,3); for($i=0;$i<3;$i++) echo $arr[$i];
Opcode