1.1.1 zval結構
Zend使用zval結構來儲存PHP變數的值,該結構如下所示:
複製代碼 代碼如下: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根據type值來決定訪問value的哪個成員,可用值如下:
IS_NULL |
N/A |
IS_LONG |
對應value.lval |
IS_DOUBLE |
對應value.dval |
IS_STRING |
對應value.str |
IS_ARRAY |
對應value.ht |
IS_OBJECT |
對應value.obj |
IS_BOOL |
對應value.lval. |
IS_RESOURCE |
對應value.lval |
根據這個表格可以發現兩個有意思的地方:首先是PHP的數組其實就是一個HashTable,這就解釋了為什麼PHP能夠支援關聯陣列了;其次,Resource就是一個long值,它裡面存放的通常是個指標、一個內部數組的index或者其它什麼只有建立者自己才知道的東西,可以將其視作一個handle。
1.1.2 引用計數
引用計數在垃圾收集、記憶體池以及字串等地方應用廣泛,Zend就實現了典型的引用計數。多個PHP變數可以通過引用計數機制來共用同一份zval,zval中剩餘的兩個成員is_ref和refcount就用來支援這種共用。
很明顯,refcount用於計數,當增減引用時,這個值也相應的遞增和遞減,一旦減到零,Zend就會回收該zval。
那麼is_ref呢?
1.1.3 zval狀態
在PHP中,變數有兩種——引用和非引用的,它們在Zend中都是採用引用計數的方式儲存的。對於非引用型變數,要求變數間互不相干,修改一個變數時,不能影響到其他變數,採用Copy-On-Write機制即可解決這種衝突——當試圖寫入一個變數時,Zend若發現該變數指向的zval被多個變數共用,則為其複製一份refcount為1的zval,並遞減原zval的refcount,這個過程稱為“zval分離”。然而,對於引用型變數,其要求和非引用型相反,引用賦值的變數間必須是捆綁的,修改一個變數就修改了所有捆綁變數。
可見,有必要指出當前zval的狀態,以分別應對這兩種情況,is_ref就是這個目的,它指出了當前所有指向該zval的變數是否是採用引用賦值的——要麼全是引用,要麼全不是。此時再修改一個變數,只有當發現其zval的is_ref為0,即非引用時,Zend才會執行Copy-On-Write。
1.1.4 zval狀態切換
當在一個zval上進行的所有賦值操作都是引用或者都是非引用時,一個is_ref就足夠應付了。然而,世界總不會那麼美好,PHP無法對使用者進行這種限制,當我們混合使用引用和非引用賦值時,就必須要進行特別處理了。
情況I、看如下PHP代碼:
複製代碼 代碼如下:<?php
$a = 1;
$b = $a;
$c = $b;
$d = &$c; // 在一堆非引用賦值中,插入一個引用
?>
這段代碼首先進行了一次初始化,這將建立一個新的zval,is_ref=0, refcount=1,並將a指向這個zval;之後是兩次非引用賦值,正如前面所說,只要把b和c都指向a的zval即可;最後一行是個引用賦值,需要is_ref為1,但是Zend發現c指向的zval並不是引用型的,於是為c建立單獨的zval,並同時將d指向該zval。
從本質上來說,這也可以看作是一種Copy-On-Write,不僅僅是value,is_ref也是受保護的對象。
整個過程圖示如下:
情況II,看如下PHP代碼:
複製代碼 代碼如下:<?php
$a = 1;
$b = &$a;
$c = &$b;
$d = $c; // 在一堆引用賦值中,插入一個非引用
?>
這段代碼的前三句將把a、b和c指向一個zval,其is_ref=1, refcount=3;第四句是個非引用賦值,通常情況下只需要增加引用計數即可,然而目標zval屬於引用變數,單純的增加引用計數顯然是錯誤的, Zend的解決辦法是為d單獨產生一份zval副本。
全過程如下所示:
1.1.5 參數傳遞
PHP函數參數的傳遞和變數賦值是一樣的,非引用傳遞相當於非引用賦值,引用傳遞相當於引用賦值,並且也有可能會導致執行zval狀態切換。這在後面還將提到。