淺談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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章