PHP 變數底層由 zval 結構體 組成,包含 value(實際資料)、refcount(引用計數)、is_ref(是否參考集合)三個關鍵字段。參考計數器實現 Copy-on-Write (COW):多個變數共享同一記憶體,只有發生寫入操作時才複製,極大節省記憶體。
| 模式 | 語法 | is_ref | refcount 變化 | 記憶體行為 |
|---|---|---|---|---|
| 值傳遞 | $b = $a | 0 | +1 | 共享記憶體(COW),寫入時分離 |
| 參考賦值 | $b = &$a | 1 | +1 | 強制綁定同一記憶體,寫入即時同步 |
$a = "a" → refcount=1, is_ref=0$b = $a → refcount=2, is_ref=0(共用一間屋,互不干涉,寫入時才分房)$b = &$a → refcount=2, is_ref=1(領證綁定,一榮俱榮一損俱損)$c = &$a → refcount=3, is_ref=1(PHP 允許一夫多妻/一妻多夫)使用 xdebug_debug_zval('var') 觀察變化(需安裝 Xdebug)。
<?php $a = "a"; // $a 獨居 $b = $a; // $a,$b 合租 (refcount=2) $va = "b"; // $va 獨居 $vb = $va; // $va,$vb 合租 (refcount=2) $a = $va; // $a 單身,$va 單身 → $a 搬去與 $va 合租 // 結果:$a,$va,$vb 三人合租 (refcount=3);$b 獨居 (refcount=1) ?>
<?php $a = "a"; // 獨居 $b = &$a; // $a,$b 結婚 $va = "b"; // 獨居 $vb = $va; // $va,$vb 合租 $a = $va; // $a 已婚不能隨便搬家 → $va 值拷貝給 $a // 結果:$a,$b 結婚 (refcount=2, 值=xyz);$va,$vb 合租 (refcount=2, 值=xyz) ?>
<?php $a = "qwe"; // 獨居 $b = $a; // $a,$b 合租 $va = "xyz"; // 獨居 $vb = &$va; // $va,$vb 結婚 $a = $va; // $a 想合租但 $va 已婚 → $a 從 $b 分離,新開房間 // 結果:$a 獨居 (refcount=1);$b 獨居 (refcount=1);$va,$vb 結婚 (refcount=2) ?>
<?php $a = "qwe"; $b = $a; // 合租 $va = "xyz"; $vb = $va; // 合租 $a = &$va; // 雙方單身但有室友 → 各自分離、新開房間結婚 // 結果:$a,$va 結婚 (refcount=2);$b 獨居;$vb 獨居 ?>
<?php $a = "qwe"; $b = $a; // 合租 $va = "xyz"; $vb = &$va; // 結婚 $a = &$va; // $a 單身想嫁給已婚 $va → $a 搬進去,重婚 // 結果:$a,$va,$vb 三人結婚 (refcount=3);$b 獨居 ?>
<?php $a = "qwe"; $b = &$a; // 結婚 $va = "xyz"; $vb = $va; // 合租 $a = &$va; // $a 已婚想嫁給單身 $va → $a 離婚、$va 搬出合租、兩人結婚 // 結果:$a,$va 結婚 (refcount=2);$b 變單身;$vb 變單身 ?>
<?php $a = "qwe"; $b = &$a; // 結婚 $va = "xyz"; $vb = &$va; // 結婚 $a = &$va; // $a 主動想嫁給 $va → $a 離婚、加入 $va 婚姻 // 結果:$a,$va,$vb 三人結婚 (refcount=3);$b 變單身 ?>
is_ref:若 1 → 值拷貝;若 0 → 檢查右側 is_ref:若 1 → 變數分離;若 0 → 直接共享記憶體(COW)is_ref:若 1 → 直接 refcount++(重婚);若 0 → 檢查右側 refcount:若 1 → 直接共享;若 >1 → 右側分離後共享。左側總是與原關係分離; 安裝 Xdebug 後在 php.ini 啟用
zend_extension=xdebug.so
xdebug.mode=debug
; 程式中隨時檢查
xdebug_debug_zval('a');
xdebug_debug_zval('b');
function(&$arr) 避免複製unset($var) 僅斷開變數名綁定,refcount--;歸零時才釋放記憶體$var = null 會強制將該 zval refcount=0 並標記回收,影響所有共享者 → 慎用gc_collect_cycles()深入理解參考計數器,能讓你在處理大數據、長時間運行的 CLI 腳本、高併發場景時,精準控制記憶體佔用,避免「記憶體洩漏」與「過早複製」的效能陷阱。