標籤:欄位 複製 成員變數 怎麼辦 引用計數 設定 固定 整數 聯合
php是弱類型語言,它可以儲存任何的資料類型。但是php是使用c語言編寫的,而c語言是強型別語言。每個變數都有固定的類型,不能隨意改變變數的類型。
在zend/zend.h中,查看結構體:
zval結構體就是通常用到的php變數在核心總的表示形式,在zval結構體中,可以看到四個成員變數,分別是:
zvalue_value value:變數的值,php變數的值就儲存在這裡。
zend_uint refcount:變數引用數,變數引用計算機。
zend_uchar type:變數的類型。
zend_uchar is_ref:變數是否被引用。
zval結構體的value成員變數是zvalue_value聯合體,php能夠保持任何的結構類型就因為這個聯合體。從zvalue_value聯合體的成員變數中可以看到,不同的類型會儲存到不同的成員變數中,這樣就實現了php變數可以儲存任何資料類型。例如,當變數是整數類型時,會儲存到value的lval成員變數中,而當變數的類型是字串時,又回儲存到value的str成員變數中。
以上是解決了php變數可以儲存任意類型的問題,但是zend引擎是怎麼知道這個變數儲存的是什麼類型呢,在zval結構中有個type成員變數,這個成員變數就是儲存一個php變數的類型。
我們都知道,php是不支援指標的,但是如果希望兩個變數同事指向同一塊記憶體怎麼辦?為瞭解決這個問題,php核心中使用了引用計數器。
zval結構中有兩個成員變數用於引用計數器:
is_ref:bool值,標識變數是否是引用集合
refcount:計算指向引用集合的變數個數
<?php
$a = "this is leju";
?>
一個zval結構的實體稱為zval容器,在php語言層建立一個變數就會相應的在php核心中建立一個zval容器。因為上面的代碼建立了一個變數$a,所以在PHP核心中會建立一個zval容器。又因為這個變數不是一個引用,所以zval容器的is_ref等於false,並且refcount等於1.
<?php
$a = "this is leju";
$b = $a;
?>
上面這段代碼中建立了兩個變數 $a和$b,所以php核心中會建立兩個zval容器來儲存它們,變數b被賦予變數a的值,由於變數b並不是引用a,所以變數a的is_ref變數的值是false,但是使用xdebug列印變數a的話,會發現refcount等於2,這是為啥呢?
首先來瞭解下php寫時複製(copy on write)機制。
寫時複製是一個解決記憶體複用的方法,例如上面的代碼,如果簡單的把a的值賦值給b,那麼就又兩個 this is leju 字串的複製,這樣不利於記憶體的複用,因為完全可以使用一個 this is leju的字串的複製完成工作,所以簡單的賦值是非常耗記憶體的,寫時複製就是為瞭解決這種問題而創造的,那什麼是寫時複製呢,就是當變數的值改變時才進行的記憶體的複製。
當將變數a的值賦值給變數b時,變數a的refcount增加1,所以這時候變數a和變數b是指向同一記憶體塊的,當改變變數a的值時,發現refcount的值變回1,所以這個時候變數a和變數b指向不同的記憶體塊,這就是寫時複製機制,就是兩個指向同一記憶體塊的變數,當其中一個變數的值發生變化,才會另外建立一個記憶體塊去儲存新的值,其實寫時複製也是一種引用,只不過這種引用會受變數值的改變而破壞罷了。
如果顯示的引用變數,即$b = &a;變數的is_ref欄位會設定1,表示此變數被引用,另外引用計數器(refcount)也相應的加1,在php核心中通過以下代碼判斷是否複製變數:
if ((*varval)->is_ref || (*varval)->refcount < 2){
return *varval;
}
推薦一本書,《PHP核心技術與最佳實務》 非常好看~
php核心中的變數