浅谈PHP7中的ZVAL

我们都知道,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中的字段信息如下:

  1. next: 用来解决哈希问题,记录冲突的下一个元素位置。
  2. cache_slot: 运行时缓存。在执行函数时会优先去缓存中查找,若缓存中没有,会在全局的function表中查找。
  3. lineno: 文件执行的行号,应用在AST节点上。Zend引擎在词法和语法解析时会将当前的文件的行号记录下来,记录在zend_ast中的lineno中,如果zend_ast这个节点的kind刚好是ZEND_AST_ZCAL(值为64),则会将zend_ast强制转换成zend_ast_zval类型,而对应的lineno则记录在zend_ast_zval结构体中内嵌的zval里。
  4. num_args: 函数调用时传入参数的个数
  5. fe_pos: 遍历数组时的当前位置,比如在对数组执行foreach时,fe_pos每执行一次都会加1。当再次调用foreach对数组遍历时,会首先对数组的fe_pos指针重置。
  6. fe_iter_idx: 跟fe_pos用途类似,只有这个字段里针对对象的,对象的属性也是HashTable,传入的参数是对象时,会获取对象的属性表,因此遍历对象就是遍历对象的属性。
  7. access_flags: 对象类的访问标志,常用的标识有public、protected、private。
  8. 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

猜你喜欢

转载自blog.csdn.net/weixin_43885417/article/details/100990106