php变量赋值默认是“写时复制”,即$a = $b不立即拷贝数据,仅共享zval;修改时才分离;对象赋值拷贝句柄,数组赋值为值语义但含对象时仍浅拷贝。

PHP变量赋值是值拷贝还是引用传递?
PHP变量赋值默认是“写时复制”(Copy-on-Write),既不是纯值拷贝,也不是默认引用。变量名背后是zval结构体,多个变量可以指向同一个zval,直到其中一方尝试修改——此时才真正复制数据。
这意味着:$a = $b 这一行几乎不消耗内存,也不触发深拷贝;但一旦你对$a做++、.=或数组元素修改,PHP内部才会分离zval。
- 标量类型(
int、string、bool)和小数组在赋值时不立即复制 - 大字符串(>4KB)或嵌套很深的数组,可能因内存管理策略提前分离
- 对象在 PHP 5.0+ 默认是“引用语义”:即
$a = $b后,两者操作同一对象实例——但这不是引用赋值,而是对象标识符(object handle)的拷贝
什么时候必须用 & 才算真引用?
只有显式使用&才能建立变量间的硬引用关系,让它们始终共享同一份存储位置。这种引用和 C 的指针不同,是 PHP 自己维护的符号表绑定。
典型误判场景:以为foreach ($arr as $v)里的$v能修改原数组元素——其实不能,除非写成foreach ($arr as &$v)。
立即学习“PHP免费学习笔记(深入)”;
-
$a = &$b后,改$a或$b都会影响对方 - 函数参数加
&$param可让函数内修改影响外部变量,但 PHP 8.0+ 已弃用“调用处加&”的写法(如foo(&$x)) - 返回引用需函数声明为
function &foo(),否则即使return $var也不会产生引用效果 - unset 引用变量不会销毁原始值,只断开绑定;所有引用都 unset 后,zval 才被回收
数组赋值时的“浅拷贝”陷阱
PHP 数组是值语义,但“值”里如果包含对象,那拷贝的只是对象句柄——表面看是深拷贝,实际是浅的。这是最容易踩坑的地方。
比如:$a = ['user' => new StdClass()]; $b = $a; $b['user']->name = 'Alice'; —— 此时$a['user']->name也变成'Alice',因为对象没被复制。
- 需要真正隔离对象,得手动克隆:
$b = ['user' => clone $a['user']] - 递归克隆整个数组可用
unserialize(serialize($arr)),但性能差、不支持资源/闭包 - PHP 7.4+ 可用
ArrayObject配合getArrayCopy(),但仍是浅拷贝 - 注意
array_merge()、+运算符对数组的合并逻辑不同:前者会重排数字键,后者保留左侧键名
调试时怎么确认两个变量是否共用 zval?
靠===或==没法判断是否共享底层存储;得用xdebug_debug_zval()(需 Xdebug)或debug_zval_dump()(PHP 内置,但输出易误解)。
debug_zval_dump($a)会显示refcount和is_ref字段:refcount=2表示至少有两个变量指向它;is_ref=1表示至少有一个是硬引用(&绑定)。
-
refcount包含循环引用计数器,不一定等于“变量个数”,尤其在闭包或对象属性中 -
is_ref=0不代表没共享,只是没显式用&;is_ref=1则一定共享且不可被写时复制打断 - 没有 Xdebug 时,可借助
spl_object_id()判断对象是否同一实例,但对数组/字符串无效
unset、函数返回、作用域退出时,PHP 都在默默调整 refcount。真正出问题往往不是赋值本身,而是预期和实际的共享行为不一致——尤其是混用对象、引用和数组的时候。











