我們都知道,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