我们都知道PHP是一个弱类型语言,它的变量理论上可以存储任何类型的数据。
那么,PHP的变量在内核中究竟是怎么实现的呢?
在PHP内核中,变量称为zval,变量的值称为zend_value,注意这是两个不同的东西。
PHP中变量的内存是通过引用计数的方式进行管理的,在PHP7之前,zval容器中有两个字节的额外信息,一个是"is_ref",是个bool值,用来标识这个变量是否是属于引用集合(reference set)。通过这个字节,php引擎才能把普通变量和引用变量区分开来。另一个是"refcount",用以标识指向这个zval变量容器的变量个数。
zend.h中的定义如下(PHP7之前的版本):
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
而PHP7发布之后把这引用计数信息放到了zend_value中,其变量之间的传递和赋值一般也是针对zend_value。
我们下面的讨论如无特殊说明,都是基于PHP7。
PHP7之后zval变化较大,我们先来看变量基础结构的定义
typedef 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 u1和u2
u1中u1.v.type可以用来区分变量的类型,u1.v.type_flags是类型掩码,在变量的内存管理、gc机制中会用到。
u2是个辅助值,假如zval只有value、u1两个值,整个zval的大小也会对齐到16byte,既然不管有没有u2大小都是16byte,把多余的4byte拿出来用于一些特殊用途还是很划算的,比如next在哈希表解决哈希冲突时会用到,还有fe_pos在foreach会用到。
注意因为C 语言联合体的特征是一次只有一个成员是有效的并且分配的内存与需要内存最多的成员匹配(也要考虑内存对齐)。所以在zend_value中,只有zend_long和double是直接存储值的,而其他类型都是指向具体数据结构的指针。这可以避免在声明变量而未赋值时,造成过多的内存消耗
zval.u1.v.type对应的type类型如下:
/* regular data types */
#define IS_UNDEF 0
#define IS_NULL 1
#define IS_FALSE 2
#define IS_TRUE 3
#define IS_LONG 4
#define IS_DOUBLE 5
#define IS_STRING 6
#define IS_ARRAY 7
#define IS_OBJECT 8
#define IS_RESOURCE 9
#define IS_REFERENCE 10
/* constant expressions */
#define IS_CONSTANT 11
#define IS_CONSTANT_AST 12
/* fake types */
#define _IS_BOOL 13
#define IS_CALLABLE 14
/* internal types */
#define IS_INDIRECT 15
#define IS_PTR 17
注意其中的标量类型true、false、long、double、null,他们当中true、false、null没有value,直接根据type区分,而long、double的值则直接存在value中:zend_long、double,也就是标量类型不需要额外的value指针。
参考资料:
https://github.com/pangudashu/php7-internal/blob/master/2/zval.md
http://php.net/manual/zh/features.gc.refcounting-basics.php