我们都知道,PHP中的变量都存储在一个叫zval的结构体中。
在聊php7中的zval之前,我们先回顾一下php5中zval。
struct _zval_struct{
/* 变量信息 */
zvalue_value value; // value
zend_uint refcount__gc; //计数
zend_uchar type; //类型
zend_uchar is_ref__gc; //是否为引用
}
typedef union _zvalue_value {
long lval; // 用于 bool 类型、整型和资源类型
double dval; // 用于浮点类型
struct { // 用于字符串
char * val; //字符串值
int len; //长度
} str;
HashTable *ht; //HashTable数组
zend_object_value obj; //对象
zend_ast *ast; //常量表达式
} zvalue_value;
根据上面的结构体,我们很清晰的知道PHP5中的ZVAL的基本内容。在这里解释一下 C语言中的union 联合体,它的所有成员共用一块内存空间。
然后再看一下PHP7改写后的zval:
struct _zval_struct {
zend_value value; /* value */
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, /*标明zval类型*/
zend_uchar type_flags,
zend_uchar const_flags,
zend_uchar reserved)
} v;
uint32_t type_info;
} u1;
union {
uint32_t var_flags;
uint32_t next; /* 用来解决哈希冲突 */
uint32_t cache_slot; /* 运行时缓存 */
uint32_t lineno; /* 对于zend_ast_zcal存行号 */
uint32_t num_args; /* EX(This)参数个数 */
uint32_t fe_pos; /* foreach的位置 */
uint32_t fe_iter_idx; /* foreach游标的标记 */
uint32_t access_flags; /* 类的常量访问标识 */
uint32_t property_guard; /* 单一属性保护 */
} u2;
};
/* value 字段结构体*/
typedef union _zend_value {
zend_long lval; /* 整型 */
double dval; /* 浮点型 */
zend_refcounted *counted; /* 引用计数 */
zend_string *str; /* 字符串类型 */
zend_array *arr; /* 数组类型 */
zend_object *obj; /* 对象类型 */
zend_resource *res; /* 资源类型 */
zend_reference *ref; /* 引用类型 */
zend_ast_ref *ast; /* 抽象语法树 */
zval *zv; /* zval类型 */
void *ptr; /* 指针类型 */
zend_class_entry *ce; /* class类型 */
zend_function *func; /* function类型 */
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
从上面可以看出value支持更多的类型。除了value字段之外,zval结构体还有两个重要的字段u1和u2,它们都是联合体结构,却各有用途。
u1中的字段含义:
type: 记录变量类型
type_flag: 对应变量类型的特有标记,不同类型的变量对应的flag也不同。所对应的标记如下:
/* zval.u1.v.type_flags */
IS_TYPE_CONSTANT //是常量类型
IS_TYPE_IMMUTABLE //不可变的类型,比如存在共享内存中的数组
IS_TYPE_REFCOUNTED //需要引用计数的类型
IS_TYPE_COLLECTABLE //可能包含循环引用的类型(IS_ARRAY, IS_OBJECT))类型
IS_TYPE_COPYABLE //是常量类型
const_flag: 常量类型的标记,对应的属性有:
/* zval.u1.v.const_flags */
#define IS_CONSTANT_UNQUALIFIED 0X010
#define IS_CONSTANT_VISITED_MARK 0X020
#define IS_CONSTANT_CLASS 0X080 /* __CLASS__ trail类 */
#define IS_CONSTANT_IN_NAMESPACE 0X100 /* 只能在opline->extended_value */
reserved: 保留字段。
那么u2中的字段信息如下:
- next: 用来解决哈希问题,记录冲突的下一个元素位置。
- cache_slot: 运行时缓存。在执行函数时会优先去缓存中查找,若缓存中没有,会在全局的function表中查找。
- lineno: 文件执行的行号,应用在AST节点上。Zend引擎在词法和语法解析时会将当前的文件的行号记录下来,记录在zend_ast中的lineno中,如果zend_ast这个节点的kind刚好是ZEND_AST_ZCAL(值为64),则会将zend_ast强制转换成zend_ast_zval类型,而对应的lineno则记录在zend_ast_zval结构体中内嵌的zval里。
- num_args: 函数调用时传入参数的个数
- fe_pos: 遍历数组时的当前位置,比如在对数组执行foreach时,fe_pos每执行一次都会加1。当再次调用foreach对数组遍历时,会首先对数组的fe_pos指针重置。
- fe_iter_idx: 跟fe_pos用途类似,只有这个字段里针对对象的,对象的属性也是HashTable,传入的参数是对象时,会获取对象的属性表,因此遍历对象就是遍历对象的属性。
- access_flags: 对象类的访问标志,常用的标识有public、protected、private。
- property_guard: 防止类中的魔术方法的循环调用,比如__get、__set等。
字符串类型:
struct _zend_string {
zend_refcounted_h gc;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
资源类型:
struct _zend_resource {
zend_refcounted_h gc;
int handle;
int type;
void *ptr;
}
资源类型使用的地方比较广泛,在使用时根据不同的类型对void * 指针进行强制转换。
对象:
typedef struct _zend_object_value {
zend_object_handle handle;
const zend_object_handlers *handlers;
}zend_object_value;
handle是一个无符号int,通过handle可以在全局的对象池里索引到指定对象。handlers指向一个包含多个函数的指针的结构体,如对象的析构、释放、读属性等操作函数。但是对象的真正数据并没有在这里,而是存在全局的EG(objects_store)中。
下一篇讲解PHP GC