在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