陣列含 3 個長字串,分別測試三種 foreach 寫法的內存增量。
$m1 = memory_get_usage();
$arr = explode("\n", str_repeat("aaaaaaaaaaaaaa\n", 3));
foreach ($arr as $v) { $count++; } // 唯讀
$m2 = memory_get_usage();
echo $m2 - $m1; // 788 bytes(極小,無複製)
$m1 = memory_get_usage();
$arr = explode("\n", str_repeat("aaaaaaaaaaaaaa\n", 3));
foreach ($arr as $v) {
$count++;
$v = 'aaaaaaaaaaaaaa'; // 寫入操作
}
$m2 = memory_get_usage();
echo $m2 - $m1; // 840 bytes(增長 ~52 bytes,觸發分離)
$m1 = memory_get_usage();
$arr = explode("\n", str_repeat("aaaaaaaaaaaaaa\n", 3));
foreach ($arr as &$v) { // & 參考
$count++;
// $v = '...'; // 即使寫入也無增量
}
$m2 = memory_get_usage();
echo $m2 - $m1; // 788 bytes(與唯讀值傳遞相同)
| 寫法 | 唯讀 | 寫入 | 說明 |
|---|---|---|---|
as $v | 共享內存 | 觸發 COW 分離 | 預設行為,寫入時才複製 |
as &$v | 共享內存 | 直接修改原陣列 | 無分離,但修改影響原陣列 |
as $v:每次迴圈將陣列元素 zval 的 refcount++,迴圈結束 refcount--。寫入時引擎檢測 refcount>1 執行分離。as &$v:直接綁定元素 zval,is_ref=1,無分離機制,修改即時生效於原陣列。as $v,享受 COW 零複製優勢。as &$v 避免分離開銷,但迴圈結束後 必須 unset($v) 解除參考,否則後續誤用 $v 會污染最後一個元素。as $v 寫入(觸發分離)或迴圈前 $arr = $arr; 強制複製。$arr = [1,2,3];
foreach ($arr as &$v) { $v *= 2; }
unset($v); // 關鍵!
foreach ($arr as $v) { echo $v; } // 正常輸出 2 4 6
// 若無 unset($v),第二個迴圈 $v 仍參考 $arr[2],賦值會修改原陣列
理解 foreach 中 COW 的觸發條件,能在處理大數據時做出正確選擇:唯讀用值傳遞、修改原陣列用參考並 unset、修改副本用值傳遞寫入。