变量的根本构造

//zend_types.htypedef struct _zval_struct zval;typedef union _zend_value { zend_long lval; //int整形 double dval; //浮点型 zend_refcounted counted; zend_string str; //string字符串 zend_array arr; //array数组 zend_object obj; //object工具 zend_resource res; //resource资源类型 zend_reference ref; //引用类型,通过&$var_name定义的 zend_ast_ref ast; //下面几个都是内核利用的value zval zv; void ptr; zend_class_entry ce; zend_function func; struct { uint32_t w1; uint32_t w2; } ww;} zend_value;struct _zval_struct { zend_value value; //变量实际的value union { struct { ZEND_ENDIAN_LOHI_4( //这个是为了兼容大小字节序,小字节序便是下面的顺序,大字节序则下面4个顺序翻转 zend_uchar type, //变量类型 zend_uchar type_flags, //类型掩码,不同的类型会有不同的几种属性,内存管理会用到 zend_uchar const_flags, zend_uchar reserved) //call info,zend实行流程会用到 } v; uint32_t type_info; //上面4个值的组合值,可以直接根据type_info取到4个对应位置的值 } u1; union { uint32_t var_flags; uint32_t next; //哈希表中办理哈希冲突时用到 uint32_t cache_slot; / literal cache slot / uint32_t lineno; / line number (for ast nodes) / uint32_t num_args; / arguments number for EX(This) / uint32_t fe_pos; / foreach position / uint32_t fe_iter_idx; / foreach iterator index / } u2; //一些赞助值};

zval构造比较大略,内嵌一个union类型的zend_value保存详细变量类型的值或指针,zval中还有两个union:u1、u2:

u1: 它的意义比较直不雅观,变量的类型就通过u1.v.type区分,其余一个值type_flags为类型掩码,在变量的内存管理、gc机制中会用到(之前分享的垃圾回收机制中,变量的type_flags只有包含IS_TYPE_COLLECTABLE的变量才会被GC网络)

php7变量PHP7变量的内部实现 Node.js

u2: 这个值纯粹是个赞助值,如果zval只有:value、u1两个值,全体zval的大小也会对齐到16byte,既然不管有没有u2大小都是16byte,把多余的4byte拿出来用于一些分外用场还是很划算的,比如next在哈希表办理哈希冲突时会用到,还有fe_pos在foreach会用到......

从zend_value可以看出,除long、double类型直接存储值外,其它类型都为指针,指向各自的构造。

类型

标量类型

最大略的类型是true、false、long、double、null,个中true、false、null没有value,直接根据type区分,而long、double的值则直接存在value中:zend_long、double,也便是标量类型不须要额外的value指针。

字符串

PHP中字符串通过zend_string表示:

struct _zend_string { zend_refcounted_h gc; zend_ulong h; / hash value / size_t len; char val[1];};

gc: 变量引用信息,比如当前value的引用数,所有用到引用计数的变量类型都会有这个构造,3.1节会详细剖析

h: 哈希值,数组中计算索引时会用到

len: 字符串长度,通过这个值担保二进制安全

val: 字符串内容,变长struct,分配时按len长度申请内存

数组

array是PHP中非常强大的一个数据构造,它的底层实现便是普通的有序HashTable,这里大略看下它的构造。

typedef struct _zend_array HashTable;struct _zend_array { zend_refcounted_h gc; //引用计数信息,与字符串相同 union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar flags, zend_uchar nApplyCount, zend_uchar nIteratorsCount, zend_uchar reserve) } v; uint32_t flags; } u; uint32_t nTableMask; //打算bucket索引时的掩码 Bucket arData; //bucket数组 uint32_t nNumUsed; //已用bucket数 uint32_t nNumOfElements; //已有元素数,nNumOfElements <= nNumUsed,由于删除的并不是直接从arData中移除 uint32_t nTableSize; //数组的大小,为2^n uint32_t nInternalPointer; //数值索引 zend_long nNextFreeElement; dtor_func_t pDestructor;};工具/资源struct _zend_object { zend_refcounted_h gc; uint32_t handle; zend_class_entry ce; //工具对应的class类 const zend_object_handlers handlers; HashTable properties; //工具属性哈希表 zval properties_table[1];};struct _zend_resource { zend_refcounted_h gc; int handle; int type; void ptr;};

引用

在PHP中通过&操作符产生一个引用变量,也便是说不管以前的类型是什么,&首先会创建一个zend_reference构造,其内嵌了一个zval,这个zval的value指向原来zval的value(如果是布尔、整形、浮点则直接复制原来的值),然后将原zval的类型修正为IS_REFERENCE,原zval的value指向新创建的zend_reference构造。

struct _zend_reference { zend_refcounted_h gc; zval val;};

构造非常大略,除了公共部分zend_refcounted_h外只有一个val,举个示例看下详细的构造关系:

$a = \"大众time:\公众 . time(); //$a -&gt; zend_string_1(refcount=1)$b = &$a; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)

终极的结果如图:

image.png

把稳:引用只能通过&产生,无法通过赋值通报,比如:

$a = \"大众time:\"大众 . time(); //$a -> zend_string_1(refcount=1)$b = &$a; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)$c = $b; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=2) //$c -> ---

b = &a这时候a、b的类型是引用,但是c = b并不会直接将b赋值给c,而是把b实际指向的zval赋值给c,如果想要$c也是一个引用则须要这么操作:

$a = \公众time:\公众 . time(); //$a -> zend_string_1(refcount=1)$b = &$a; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)$c = &$b;/或$c = &$a/ //$a,$b,$c -> zend_reference_1(refcount=3) -> zend_string_1(refcount=1)

这个也表示PHP中的引用只可能有一层 ,不会涌现一个引用指向其余一个引用的情形 。