理解 PHP 變量在內存中的真實行為,是寫出高效、無 Bug 代碼的基石。本文從 Zend 引擎內存模型出發,深入剖析值類型(Scalar、Array)與引用類型(Object)的賦值、傳參、複製行為差異,並給出實戰最佳實踐。
PHP 7+ 使用 zval 結構體存儲變量值,關鍵字段:
value:實際數據(整數、浮點、字符串指針、陣列指針、對象指針)type:類型標記(IS_LONG, IS_STRING, IS_ARRAY, IS_OBJECT 等)refcount:引用計數(有多少變量名指向此 zval)is_ref:是否為引用集合(由 & 產生)<?php $a = 1; // zval(refcount=1, is_ref=0, value=1) $b = $a; // zval(refcount=2, is_ref=0) 共享同一 zval $a = 2; // 寫入觸發 COW:分離新 zval(refcount=1, value=2),$b 仍指向舊 zval(value=1) echo $b; // 輸出 1
refcount,不複製數據<?php
class User { public $name = 'Tom'; }
$a = new User; // $a 存儲對象句柄(handle),指向對象實例
$b = $a; // 複製句柄,$a 和 $b 指向同一實例
$a->name = 'Jim';
echo $b->name; // 輸出 Jim
clone 關鍵字| 場景 | 語法 | 效果 |
|---|---|---|
| 變量別名 | $b = &$a; | $a 和 $b 綁定同一 zval (is_ref=1),任意一方修改即影響另一方 |
| 函數引用傳參 | function foo(&$x) {} | 實參與形參綁定同一 zval,函數內修改影響外部 |
| 函數引用返回 | function &bar() {} | 返回值為變量別名,可被賦值修改內部狀態 |
| 操作 | 值類型 (int/array) | 對象 |
|---|---|---|
$b = $a | COW 共享,寫時分離 | 共享同一實例 |
$b = &$a | 強綁定,完全同步 | 同樣強綁定(句柄同步) |
函數傳參 foo($a) | COW 共享 | 共享實例 |
函數傳參 foo(&$a) | 強綁定 | 強綁定 |
複製 clone $a | 語法錯誤 | 淺拷貝新實例 |
unset($a) | 減少 refcount,歸零則銷毀 | 減少 refcount,句柄釋放 |
&。foreach ($arr as &$v) { $v *= 2; }
unset($v); // 必須 unset,否則 $v 仍引用最後一個元素,後續循環會污染數據
function foo(User $u) { $u = new User; } 能改變外部變量指向,這只改變局部句柄。$b = &$a 仍能建立別名,修改任意一方會觸發 COW 分離新字符串。&,COW 已經優化&clone 為淺拷貝,深拷貝需自行實現 __clone() 魔術方法PHP 變量分為「值類型(COW)」與「對象句柄(共享實例)」,& 強制建立別名打破預設行為。掌握 refcount/is_ref 內存模型,即可精準預測賦值、傳參、複製、銷毀的每一步行為,寫出零驚喜的代碼。