變量的基礎(chǔ)結(jié)構(gòu)
我們都知道PHP的變量是弱類型的,聲明的時候無需指定類型。那么這里面具體是怎么實現(xiàn)的呢?這就得從變量的基礎(chǔ)結(jié)構(gòu)說起了。
zval的實現(xiàn)
在源碼文件 zend_type.h 中,可以看到 zval 的定義:
typedef struct _zval_struct zval; struct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t next; /* hash collision chain */ 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 */ uint32_t access_flags; /* class constant access flags */ uint32_t property_guard; /* single property guard */ uint32_t extra; /* not further specified */ } u2; }
zval 的結(jié)構(gòu)由一個保存變量類型的值或指針的 union 聯(lián)合體 zend_value 以及兩個 union 聯(lián)合體 u1 和 u2 組成
- u1
u1的作用是用來保存變量類型及其信息,其里面的字段用處如下:
type:記錄變量類型。 即可通過 u2.v.type 來訪問到
type_flags:對應(yīng)變量特有類型的標(biāo)記(如常量類型,需引用計數(shù)類型,不可變類型),不同類型的變量對應(yīng)的 flag 不一樣。
const_flags:常量類型的標(biāo)記
reserved:保留字段
- u2
u2 主要是輔助作用,由于結(jié)構(gòu)體的內(nèi)存對齊,所以 u2 的的這塊空間有或者沒有 u2 都是已經(jīng)占據(jù)空間了,所以就利用起來。u2的輔助字段里面記錄了很多類型信息,這些信息對內(nèi)部功能有很大的好處,或提升緩存友好性或減少了內(nèi)存尋址的操作。這里介紹其中部分字段。
next:用來解決哈希沖突問題(哈希沖突這個目前還不懂),記錄沖突的下一個元素位置。
cache_slot:運行時緩存。在執(zhí)行函數(shù)時會優(yōu)先去緩存中查找,若緩存中沒有,再去全局的 function 表中查找。
num_args:函數(shù)調(diào)用時傳入?yún)?shù)的個數(shù)
access_flags:對象類的訪問標(biāo)識,如public protected private 這些。
- zend_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; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
從 zend__value 中可以看出,long、double 類型直接存儲值,而其它類型都為指針,指向各自的結(jié)構(gòu)。所以,由于 zval 這樣的結(jié)構(gòu),PHP 變量在聲明的時候不用顯示的指定其類型,因為不管你賦給變量什么類型的值,它都能幫你找到對應(yīng)的存儲結(jié)構(gòu)。
以值為字符串的變量為例,其結(jié)構(gòu)是這樣的:
PHP5 與 PHP7 的 zval 結(jié)構(gòu)對比
- PHP5
- PHP7
可以看到 php7 的 zval 總的只占 16 個字節(jié),相比 PHP5 的 zval 所占用的 48 個字節(jié)節(jié)省了很大的內(nèi)存。
此外,在 PHP5 中,所有的變量都在堆中申請,但是對于臨時變量來說,沒有必要在堆中申請。所以在 PHP7 中對此做了優(yōu)化,臨時變量是直接在棧中申請的。
常見變量類型
下面介紹幾個常見類型的變量結(jié)構(gòu),其他