PHP底层实现

来源:互联网 发布:阿里云代码托管 收费 编辑:程序博客网 时间:2024/05/21 05:59

PHP代码的执行过程:

-知其然知其所以然

PHP虽然是脚本语言,但是不是靠解释器解释的,而是靠zend虚拟机
PHP代码编译成了opcode,由zend虚拟机老执行opcode
但是opcode在PHP脚本执行结束后就会被自动清楚(跟java不同,java中的.class文件会存留下来)

  • 思考:

opcode能否缓存?
PHP本身不支持但是其余加速器工具支持,能实现这一功能。

PHP底层是用C语言来写的,但是C语言是强类型语言,PHP是弱类型语言,如何实现的呢?

解压PHP的源码包会看到相应文件夹:
最核心的—Zend目录, 这是zend虚拟的实现. 包括栈,数据类型,编译器等,都在这实现.
最主要的main –PHP的一些内建函数,最主要函数都在这里放着.
最大的一个目录 ext – PHP的扩展.

Zend对变量的表示:
每个变量由变量名和变量值组成;
变量名存放在一个符号表中,符号表是一张哈希表,存放着变量的名和变量值的地址映射,类似于关联数组,形成一一对应的关系。
变量值存放在一个zval结构体中

{value: [联合体] ,联合体的内容可能是C语言中的long,double,hashtable...type: 变量类型 , IS_NULL,IS_BOOL,IS_STRING...... IS_RESOURCErefcount_gcis_ref_gc }
如:$a = 3;{value : [long lval = 3]type: IS_LONG}$a = 3.5{value: [double dval = 3.5]type:IS_DOUBLE}
typedef union _zvalue_value{    long lval;   /*long型值*/    double dval;  /*double型值*/    struct{        char *val;        int len;    }str;    HashTable *ht;  /*hash table 值*/    zend)object_value obj;}zvalue_value;
  • 疑问:

PHP中有8中数据类型(bool int string float , obj array ,resource null),为什么zval->value联合体中只有5种?
1:NULL,直接看zval->type=IS_UNLL,则跳过,不必设置value的值;
2:bool型,zval->type=IS_BOOL,再设置值为1或0;
3:Resources型,资源型,往往是在服务器上打开一个接口,如果 文件读取接口
zval->type = IS_RESOURCE, zval->tyoe.lval = 服务器上打开的接口的编号

  • 发现:
    PHP中,字符串类型,长度是已经缓存的,调用strlen时,系统可以直接返回其长度,不必计算。

变量的赋值与引用

在赋值时,值传递,并没有再次产生结构体,而是2个变量公用1个结构体,此时两个变量地址指向一个结构体,结构体中refcount_gc值为2。

  • 思考:
    a,b指向同一个结构体,那么,修改a或者b对方会不会受干扰?
    不会。
    因为2者有一方修改时就会造成结构体的分裂,这种特点称为cow(copy on write),写时复制

当变量引用赋值时,双方共用一个结构体,此时结构体中is_ref_gc值自增1,初始值为0。
引用赋值时不会出现写时复制,而是两个变量共享一个结构体,都有操作它的权利。

引用时的一些怪现象

<?php$a = array(2,3,4,5);$tem = $a;$x = &$a[1];$a[1] = 999;echo $tem[1]; //3
<?php$a = array(2,3,4,5);$x = &$a[1];$tem = $a;$a[1] = 999;echo $tem[1]; //999

循环数组时的怪现象

循环遍历数组时,遍历的不是数组本身,而是在内存中复制了一份arraycopy,
如果遍历时有写操作,则会发生写时复制,指针重新指向分离出的数组顶端。

<?php$arr = array('a','b','c','d');foreach ($arr as &$v){    //空操作}//相当于$v = &$arr[3];foreach ($arr as $v){    //arrcopy的每一个值取出来赋给$v    print_r($arr);}//分别是arrcopy的[0],[1],[2],[3]赋给了$v,//arrcopy[0]是a,a赋给了$v,而$v是引用的arr[3];所以arr[3]值收到了影响,-->abca,依次循环
  • 注意
    数组使用时,慎用引用;
    foreach使用后,不会把数组的内部指针重置,使用数组时,不要假象内部指针正好指向头部。
    也可以在foreach后reset一下数组,重置指针。

函数执行时的栈变化

函数在执行时,编译过后会根据函数的参数,局部变量等,生成一个执行环境的结构体,结构体入栈,
_zend_execute_data {}结构体
这个结构体中,还有2个重要的信息:
{
*op_array ——>是函数的执行步骤
*hash_table—->symbol_table 这个函数对应的符号表
}
函数编译后的opcode称为op_array(执行逻辑),开始执行——以入栈的环境结构体为环境来执行。

  • 注意
    一个函数可能调用多次,栈中可能有某函数的多个执行环境入栈,但这个函数的op_array只有一个。

  • 思考
    一个函数,递归调用自己三次,在栈上肯定有三个execute_data生成,但是这三个execute_data对用几个*op_array?
    函数编译完了生成一份*op_array,因为函数的执行逻辑是固定的。
    生成了几个几个symbol_table?
    三个符号表。

函数中的静态变量是如何形成的

一个函数编译出来值存在一份op_array,静态变量存在于执行过程op_array,并不存在于结构体入栈的符号表中。
不管调用多少次静态变量,调用的只是一个共同的op_array中的变量。

常量的实现

define一个常量时其全局符号表就一个。
变量的符号表可以有多个,但是常量的符号表就一个。

对象的实现

当每new一个对象的时候,对象统一放到了一个hash表(对象池)里面

typedef struct _zend_object_value{    zend_object_handle handle;    zend_object_handlers *handlers;}zend_object_value;

对象定义的就两个值,handle是一个指向对象池里的地址,相当于一个对象的指针,
定义一个对象其实生成了一个指针,这个指针再次指向对象。

$obj = new human();value:{handle:23851}//handle指向对象地址type:IS_OBJECTrefcount_gc:1is_ref_gc:0

对应的对象池里的存储:

23851{age:24name:lisi...}
  • 实例
<?phpclass Dog{    public $leg = 4;}$dog = new Dog();$d2 = $dog;$d2->leg = 5;echo  $dog->leg,$d2->leg;//5,5

问:$dog是一个对象吗?
$dog的内容:
{
handle —指向—->[hash表{leg:4}]
}
new的$dog指向一个handle,handle再次指向一个对象池里的Dog,
当又有了$d2,也指向这个zval(同一个handle),$d2并没有改变这个zval的值,
而是通过zval的指引改变了这个对象池里对象的值,所以$dog的值也被改变。

  • 注意
    这并不是说$d2$dog是引用传递的关系,如果$d2=false的话并不会使$dog为空,
    而是$d2自己的指向了false,$dog还是指向之前的handle。
    只是他们共同通过一个handle去指向一个对象池里的对象。

内存管理与垃圾回收

php内存机制

  • 注意

    PHP封装了对系统内存的请求,不要直接用malloc直接请求内存。

底层hash表
PHP的hashtable太强大!

0 0