從底層原理深入理解 PHP 的引用 “&”

在PHP中正確使用引用符(&)可以提高程序運行效率,並且更加節約內存空間。但是在不理解引用原理的時候濫用引用符是極其危險的,往往會造成意料之外的錯誤。

1.變量的底層存儲結構zval

PHP運行核心是Zend引擎,用純C語言實現,是PHP的內核部分,它將PHP代碼翻譯(詞法、語法解析等一系列編譯過程)爲可執行opcode的處理並實現相應的處理方法、實現了基本的數據結構(如hashtable、oo)、內存分配及管理、提供了相應的api方法供外部調用,是一切的核心,所有的外圍功能均圍繞Zend實現。(參見之前轉載的 PHP的底層運行機制與原理

zval是Zend引擎中的一個重要存儲結構,作爲變量真實的容器,用來標識並實現PHP變量。其數據結構如下:

這裏寫圖片描述

一個zval變量容器,除了包含變量的類型和值,還包括兩個字節的額外信息。第一個是”is_ref”,是個bool值,用來標識這個變量是否是屬於引用集合。第二個額外字節是”refcount”,用以表示指向這個zval變量容器的變量個數,即引用計數。

利用 xdebug 可以查看變量的 is_ref 和 refcount 值。

2. 引用傳值原理

Zend通過引用計數,多個變量可以共享同一份數據。避免頻繁拷貝帶來的大量消耗。

is_ref 記錄是否引用賦值,(取值 false 或 1)。
refcount 記錄zval被變量的引用次數,即引用計數。

在進行賦值操作時,zend將變量指向相同的zval同時ref_count++,在unset操作時,對應的ref_count-1。只有refcount減爲0時纔會真正執行銷燬操作。如果是引用賦值,則zend會修改is_ref爲1。

PHP變量通過引用計數實現變量共享數據,那如果改變其中一個變量值呢?當試圖寫入一個變量時,Zend若發現該變量指向的zval被多個變量共享,則爲其複製一份refcount爲1的zval,並遞減原zval的refcount,這個過程稱爲“zval分離”。

當 is_ref爲1 時,對指向zval的其中一個變量重新賦值,則直接改變zval的值,此時所有指向本zval的變量值都發生改變。

下面通過 xdebug 舉幾個案例:

(1)將變量值賦值給另一個變量,不會生成新的zval,而是在本zval的引用計數refcount + 1;

$a = 333;
xdebug_debug_zval('a');
$b = $a;
xdebug_debug_zval('a');

上例輸出:

a:
(refcount=1, is_ref=0),int 333
a:
(refcount=2, is_ref=0),int 333

(2)使用引用傳值賦值給另一個變量,不會生成新的zval,而是在本zval的引用計數refcount + 1,同時將is_ref改爲1;

$a = 333;
xdebug_debug_zval('a');
$b = &$a;
xdebug_debug_zval('a');

上例輸出:

a:
(refcount=1, is_ref=0),int 333
a:
(refcount=2, is_ref=1),int 333

(3)當試圖寫入一個變量時,Zend若發現該變量指向的zval被多個變量共享,則爲其複製一份refcount爲1的zval,並遞減原zval的refcount,這個過程稱爲“zval分離”。

$a = 333;
xdebug_debug_zval('a');
$b = $a;
xdebug_debug_zval('a');
$b = 444;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

上例輸出:

a:
(refcount=1, is_ref=0),int 333
a:
(refcount=2, is_ref=0),int 333
a:
(refcount=1, is_ref=0),int 333
b:
(refcount=1, is_ref=0),int 444

(4)引用傳值時,對指向zval的其中一個變量重新賦值,則直接改變zval的值,此時所有指向本zval的變量值都發生改變。

$a = 333;
xdebug_debug_zval('a');
$b = &$a;
xdebug_debug_zval('a');
$b = 444;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

上例輸出:

a:
(refcount=1, is_ref=0),int 333
a:
(refcount=2, is_ref=1),int 333
a:
(refcount=2, is_ref=1),int 444
b:
(refcount=2, is_ref=1),int 444

3.引用能做什麼

(1)引用不存在的變量會自動創建該變量(默認值爲null)。

function foo(&$var) { }

foo($a);
echo $a;     // 輸出null

$b = array();
foo($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)

$c = new StdClass;
foo($c->d);
var_dump(property_exists($c, 'd')); // bool(true)

(2)引用傳遞變量。

function foo(&$var)
{
    $var++;
}

$a=5;
foo($a);
echo $a;   //輸出6
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章