php 7.4+ 变量赋值采用引用计数与写时复制机制,$a = $b 不立即复制内存,仅增引用计数;修改时才分离副本,对象默认句柄传递,标量/数组函数内修改需加&才影响外部。

PHP中$a = $b到底拷贝了什么
PHP 7.4+ 的变量赋值不是简单“复制值”或“复制地址”,而是基于引用计数和写时复制(Copy-on-Write)的混合机制。直接说“传值”或“传引用”容易误导,关键看变量是否被修改。
常见错误现象:unset($a)后$b还能用;但$a[] = 1后$b没变——这不代表$a和$b完全独立,只是 PHP 暂未触发复制。
- 标量(
int、string、bool)和小数组在赋值时不立即复制内存,只增加引用计数 - 一旦任一变量被修改(如
$a .= "x"、$a[0] = 1),PHP 才真正分离副本 - 对象在 PHP 7+ 默认是“句柄传递”:
$a = $b让两者指向同一对象实例,修改对象属性会影响彼此
函数参数默认是传值,但对象行为像传引用
函数内部修改参数变量名本身(如$x = 123)不会影响外部,这是传值;但修改对象属性(如$x->prop = 456)会生效,因为对象变量存的是标识符(类似指针),不是数据本体。
使用场景:你写一个processUser(User $u),里面改$u->name,调用方看到变化;但如果你在函数里写$u = new User(),外部变量不变。
立即学习“PHP免费学习笔记(深入)”;
- 想真正隔离对象,得显式克隆:
$u = clone $u - 想让标量或数组在函数内修改也影响外部,必须加
&:function foo(&$arr) - PHP 8.1+ 支持
readonly类,能阻止意外修改,但不改变传递语义
unset()和return对引用计数的实际影响
unset($a)只是减少引用计数,不立刻释放内存;只有计数归零才回收。而return $a在函数结束时也会减少原作用域的引用计数——这点常被忽略,导致误判“返回后原变量还活着”。
常见错误现象:函数里return $largeArray,以为返回的是副本,其实只是转移了所有权;若外部接着$x = expensiveFunc(),那$largeArray的内存直到$x被覆盖或unset才可能释放。
- 大数组/字符串不要在函数里构造再返回,考虑用
yield或传入引用参数填充 -
debug_zval_dump($var)可查看当前引用计数(注意:它自己也会临时增加计数) -
gc_collect_cycles()不能强制清理单个变量,只回收循环引用
什么时候必须用&,什么时候根本不需要
加&不是为了“性能优化”,而是为了实现副作用——比如函数需要修改传入的配置数组、收集错误日志、或逐步构建一个结果集。90% 的日常代码不需要它。
容易踩的坑:foreach ($arr as &$item)后忘记unset($item),导致后续循环污染;或者在for循环里对&$arr[$i]赋值,结果意外改变原始结构。
- 用
foreach引用遍历时,循环结束后务必unset($item),否则$item仍绑定到最后一个元素 - 不要对函数返回值加
&:foo(&getConfig())语法错误(PHP 不允许对非变量表达式取引用) -
static变量、全局变量、超全局数组(如$_POST)本身不可被&改变作用域行为
真正复杂的地方在于:PHP 的“值”和“引用”不是二元对立,而是一套运行时根据使用方式动态调整的内存管理策略。写代码时盯着“我要不要加&”,不如先问“这个变量接下来会不会被修改”,以及“修改后我希望谁看到变化”。











