深入PHP變量存儲(chǔ)的詳解
1.1.1 zval結(jié)構(gòu)
Zend使用zval結(jié)構(gòu)來(lái)存儲(chǔ)PHP變量的值,該結(jié)構(gòu)如下所示:
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount;
zend_uchar type; /* active type */
zend_uchar is_ref;
};
typedef struct _zval_struct zval;
Zend根據(jù)type值來(lái)決定訪問(wèn)value的哪個(gè)成員,可用值如下:
IS_NULL | N/A |
IS_LONG | 對(duì)應(yīng)value.lval |
IS_DOUBLE | 對(duì)應(yīng)value.dval |
IS_STRING | 對(duì)應(yīng)value.str |
IS_ARRAY | 對(duì)應(yīng)value.ht |
IS_OBJECT | 對(duì)應(yīng)value.obj |
IS_BOOL | 對(duì)應(yīng)value.lval. |
IS_RESOURCE | 對(duì)應(yīng)value.lval |
根據(jù)這個(gè)表格可以發(fā)現(xiàn)兩個(gè)有意思的地方:首先是PHP的數(shù)組其實(shí)就是一個(gè)HashTable,這就解釋了為什么PHP能夠支持關(guān)聯(lián)數(shù)組了;其次,Resource就是一個(gè)long值,它里面存放的通常是個(gè)指針、一個(gè)內(nèi)部數(shù)組的index或者其它什么只有創(chuàng)建者自己才知道的東西,可以將其視作一個(gè)handle。
1.1.2 引用計(jì)數(shù)
引用計(jì)數(shù)在垃圾收集、內(nèi)存池以及字符串等地方應(yīng)用廣泛,Zend就實(shí)現(xiàn)了典型的引用計(jì)數(shù)。多個(gè)PHP變量可以通過(guò)引用計(jì)數(shù)機(jī)制來(lái)共享同一份zval,zval中剩余的兩個(gè)成員is_ref和refcount就用來(lái)支持這種共享。
很明顯,refcount用于計(jì)數(shù),當(dāng)增減引用時(shí),這個(gè)值也相應(yīng)的遞增和遞減,一旦減到零,Zend就會(huì)回收該zval。
那么is_ref呢?
1.1.3 zval狀態(tài)
可見(jiàn),有必要指出當(dāng)前zval的狀態(tài),以分別應(yīng)對(duì)這兩種情況,is_ref就是這個(gè)目的,它指出了當(dāng)前所有指向該zval的變量是否是采用引用賦值的——要么全是引用,要么全不是。此時(shí)再修改一個(gè)變量,只有當(dāng)發(fā)現(xiàn)其zval的is_ref為0,即非引用時(shí),Zend才會(huì)執(zhí)行Copy-On-Write。
1.1.4 zval狀態(tài)切換
當(dāng)在一個(gè)zval上進(jìn)行的所有賦值操作都是引用或者都是非引用時(shí),一個(gè)is_ref就足夠應(yīng)付了。然而,世界總不會(huì)那么美好,PHP無(wú)法對(duì)用戶(hù)進(jìn)行這種限制,當(dāng)我們混合使用引用和非引用賦值時(shí),就必須要進(jìn)行特別處理了。
情況I、看如下PHP代碼:
<?php
$a = 1;
$b = $a;
$c = $b;
?>
這段代碼首先進(jìn)行了一次初始化,這將創(chuàng)建一個(gè)新的zval,is_ref=0, refcount=1,并將a指向這個(gè)zval;之后是兩次非引用賦值,正如前面所說(shuō),只要把b和c都指向a的zval即可;最后一行是個(gè)引用賦值,需要is_ref為1,但是Zend發(fā)現(xiàn)c指向的zval并不是引用型的,于是為c創(chuàng)建單獨(dú)的zval,并同時(shí)將d指向該zval。
從本質(zhì)上來(lái)說(shuō),這也可以看作是一種Copy-On-Write,不僅僅是value,is_ref也是受保護(hù)的對(duì)象。
整個(gè)過(guò)程圖示如下:

情況II,看如下PHP代碼:
<?php
$a = 1;
$b = &$a;
$c = &$b;
?>
這段代碼的前三句將把a(bǔ)、b和c指向一個(gè)zval,其is_ref=1, refcount=3;第四句是個(gè)非引用賦值,通常情況下只需要增加引用計(jì)數(shù)即可,然而目標(biāo)zval屬于引用變量,單純的增加引用計(jì)數(shù)顯然是錯(cuò)誤的, Zend的解決辦法是為d單獨(dú)生成一份zval副本。
全過(guò)程如下所示:

1.1.5 參數(shù)傳遞
PHP函數(shù)參數(shù)的傳遞和變量賦值是一樣的,非引用傳遞相當(dāng)于非引用賦值,引用傳遞相當(dāng)于引用賦值,并且也有可能會(huì)導(dǎo)致執(zhí)行zval狀態(tài)切換。這在后面還將提到。
相關(guān)文章
基于php+redis實(shí)現(xiàn)布隆過(guò)濾器
布隆過(guò)濾器(Bloom filter)是一種用于快速判斷一個(gè)元素是否存在于集合中的數(shù)據(jù)結(jié)構(gòu),它可以有效地檢索數(shù)據(jù),而不需要存儲(chǔ)實(shí)際的元素本身,本文給大家介紹了如何基于php+redis實(shí)現(xiàn)布隆過(guò)濾器,感興趣的朋友可以參考下2023-12-12
PHP實(shí)現(xiàn)數(shù)字補(bǔ)零功能的2個(gè)函數(shù)介紹
這篇文章主要介紹了PHP實(shí)現(xiàn)數(shù)字補(bǔ)零功能的2個(gè)函數(shù)介紹,需要的朋友可以參考下2014-05-05
require(),include(),require_once()和include_once()區(qū)別
面試中最容易提到的一個(gè)PHP的問(wèn)題,我想和大家共勉一下: require()和include()有許多相似之處,也有些不同。理解它們的不同點(diǎn)非常重要,否則很容易犯錯(cuò)誤。2008-03-03
php常用數(shù)組array函數(shù)實(shí)例總結(jié)【賦值,拆分,合并,計(jì)算,添加,刪除,查詢(xún),判斷,排序】
這篇文章主要介紹了php常用數(shù)組array函數(shù),結(jié)合實(shí)例形式總結(jié)分析了php常用的數(shù)組操作函數(shù),包括數(shù)組的賦值、拆分、合并、計(jì)算、添加、刪除、查詢(xún)、判斷、排序等,需要的朋友可以參考下2016-12-12
利用PHP實(shí)現(xiàn)開(kāi)心消消樂(lè)的算法示例
開(kāi)心消消樂(lè)應(yīng)該對(duì)大家來(lái)說(shuō)都不陌生吧,下面這篇文章主要給大家介紹了關(guān)于如何利用PHP實(shí)現(xiàn)開(kāi)心消消樂(lè)算法的相關(guān)資料,文中將需求和示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10
PHP使用正則表達(dá)式實(shí)現(xiàn)過(guò)濾非法字符串功能示例
這篇文章主要介紹了PHP使用正則表達(dá)式實(shí)現(xiàn)過(guò)濾非法字符串功能,結(jié)合留言板數(shù)據(jù)提交功能示例分析了php使用正則表達(dá)式preg_replace函數(shù)進(jìn)行字符串正則替換相關(guān)操作技巧,需要的朋友可以參考下2018-06-06

