前言
PHP是解释型的语言,它的执行顺序主要会经过以下几步:
1. 进行词法分析
2. 进行语法分析
3. 通过zend编译器,编译成opcode
4. zend虚拟机执行opcode
我们在写PHP代码的时候就知道,PHP是弱语言类型,而PHP底层又是由zend虚拟机来执行的,zend虚拟机又是C语言编写的,C语言又是强类型的语言,那么PHP是怎么做到弱语言变成强语言类型的虚拟机可执行的呢?这里就要先了解到变量的zval结构了。
变量的结构
1. synbol_table符号表
synbol_table全局符号表,使用hashtable数据结构用于存放变量名以及值地址。
当我们定义一个变量 $a =1 ,a是这个变量名,1是变量的值,synbol_table就是存放变量名a和对应1所在的地址。
2. zval结构体
变量值采用的就是zval结构体来实现的,查看PHP源码可以看到zval结构体如下:
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount_gc;
zend_uchar type; /* active type */
zend_uchar is_ref_gc;
};
value: 变量值,它是一个zvalue_value联合,下面会具体介绍它的结构。
refcount_gc: 引用次数,用于变量赋值和垃圾回收使用。
type: 活动的类型,体现这个变量操作时的真实类型,它的值主要有这8种: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE IS_STRING、IS_ARRAY、IS_OBJECT、IS_RESOURCE
is_ref_gc: 是否引用变量
3. zvalue_value联合
zvalue_value联合列出了PHP语言所支持的所有变量类型,结构如下:
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
对于变量定义如下图:
当我们定义了一个变量 $a =3时,全局hashtable表中存下了变量a和3值得地址,变量值则为struct_zval_struct结构体,value值为 long类型3,type是IS_LONG类型,refcount_gc为1只有变量a在引用,is_ref_gc为0 非引用类型。
变量赋值
1. 变量传值赋值
举例:$a = 10 ; $b = $a;
我们定义了一个变量a=1,接着我们将变量a赋值给变量b。
当我们进行传值赋值时,其实php底层并不会复制,因此这时所占用的内存只有一个,从上面例子中,当我们定义变量a时则会生成一个变量a的zval结构体,这个结构体中refcount_gc=1,is_ref_gc =0,当进行赋值$b = $a时,则原来变量a的结构体中refcount_gc+1 = 2,因为非引用赋值is_ref_gc=0;
2. COW(写时复制)
COW(写时复制)特性,当变量进行写操作时则会复制新的结构体。
上面例子中$b=$a;而接下去当我们要进行 $b=20 对变量b进行修改成20时,那么会复制一份$b的结构体,此时a的结构体中 refcount_gc-1 =1 ,b的结构体refcount_gc =1。
3. 变量引用赋值
举例:$a = 10 ; $b = &$a;
同样的定义了一个变量a=10;同时变量b赋值的是变量a的地址,这时a和b都是公用同一个结构体,这个结构体中 refcount_gc=2,is_ref_gc =1。无论对a还是对b进行修改,都是修改同一个结构体。
4. 传值和引用赋值混合操作
举例:
$a=10
$b=$a
$c=&$a
$c=20
第一步:$a=10时, 结构体中 value=10 ,refcount_gc=1 ,is_ref_gc=0;
第二步:$a赋值$b时,结构体为同一个 refcount_gc =2;
第三步:$a引用赋值给$c时,结构体为同一个 refcount_gc=3, is_ref_gc=1;
第四步:$c进行修改时,则分裂一个$b的结构体 ref_count_gc=1 is_ref_gc=0,原结构体ref_count_gc=2 is_ref_gc=1;
总结
PHP虽然是解释型语言,但是实际上还是需要通过zend虚拟机进行编译成物理机所能执行的语言,所以解释型语言也是需要编译的。同时对于PHP弱语言来讲,其只是对编程人员表现出是弱语言,PHP底层依然还是强类型语言,它是通过zval来实现变量的定义和表现的。
在变量的zval结构中有一个refcount_gc的字段,这个字段也是PHP垃圾回收机制中很重要的一个标识。当我们使用unset函数时,则会将变量的refcount_gc=0,同时也会将synbol_table表中的变量也删除,这时PHP就会认定这个变量之前的结构体为垃圾数据而进行清理。