要編寫PHP擴充就不可避免要接觸PHP核心中變數的表示方式,所以必須瞭解PHP變數在核心中的儲存方式和使用方法。
-
PHP變數在核心中的儲存方式
PHP是弱類型的語言,也就是說一個PHP變數可儲存任何的資料類型,但PHP是由強型別的C語言來編寫的,為此在Zend引擎中變數的結構體如下:
-
PHP核心變數訪問宏
使用 zval.type = IS_LONG 方式可以設定一個變數的類型,不過這樣做不是很合適,因為不能預測PHP以後的版本會發生什麼變化,有可能以後版本會把type變數的名字修改成type_gc或其他,那麼之前寫好的擴充就不能適應這些版本了,為解決這個問題,PHP核心提供一個訪問和設定變數類型的方法,具體如下:
Z_TYPE(zval) 對應zval結構體的實體
Z_TYPE(&zval) 對應zval結構體的指標
Z_TYPE(&&zval) 對應zval結構體的二級指標
可以用以下方式設定變數的類型:Z_TYPE(zval) = IS_LONG,這樣,就算以後zval結構體的type成員 變數改名,我們的擴充依然可以使用,與變數的類型一樣,變數的值也有相應的訪問宏定義如:Z_LVAL(zval) = 10;給變數賦值為10.
-
引用計數器與寫時複製
PHP是不支援指標的,為解決希望兩個變數指向同一塊記憶體塊的問題,PHP核心裡使用了引用計數器。
通過上面zval的結構我們看到,zval結構中有兩個成員變數用於引用計數器:
is_ref: bool,標識變數是否是引用集合
refcount: 計算指向引用集合的變數個數
$a = 'this is a variable';
$b = $a;
上面的代碼建立了兩個變數$a,$b,在 PHP7.0 裡面在PHP核心中會建立兩個變數zval視窗來儲存它們,變數b硬拷貝了一份a的值,所以現在$a對應的zval容器的is_ref為FALSE,refcount為1.但在 PHP5.6版本 (及以前?),a的is_ref為FALSE容易理解,因為b並沒有對a進行引用,但此時a的refcount為2,這就是因為PHP5.6版本(及以前?)寫時複製的機制(copy on write),我用PHP7.0和PHP5.6測試結果證明如此。
定時複製是一個解決內在利用的方法,值相同時不建立新的記憶體來儲存新的變數,而是把新的變數引用到舊的變數的記憶體位址,只有在變數的值不同時才進行記憶體的複製。其實定時複製也是一種引用,不過這種引用會受變數的值的改變而破壞罷了。
當顯式的讓一個變數引用另一個變數的時候,變數的is_ref欄位會設定為1,表示此變數被引用,另外引用計數器也相應加1.