1. 什么是写时复制
在《php7引用计数》的文章中,我们知道,对于复制类型的变量,在赋值时,我们并没有重新复制一份数据,而是让新变量的zend_value中相应的指针指向原来的数据,同时增加引用计数。
赋值后,如果其中一个变量试图改变数据内容,就需要重新拷贝一份原数据,同时断开zend_value指向,并改变引用计数。这个过程我们称为写时复制。
下面来看一个例子:
$a = range(0,2);
$b = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
echo "after write\n";
$b[1] = 88;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
输出:
a: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=0, 1 => (refcount=0, is_ref=0)=1, 2 => (refcount=0, is_ref=0)=2)
b: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=0, 1 => (refcount=0, is_ref=0)=1, 2 => (refcount=0, is_ref=0)=2)
after write
a: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=0, 1 => (refcount=0, is_ref=0)=1, 2 => (refcount=0, is_ref=0)=2)
b: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=0, 1 => (refcount=0, is_ref=0)=88, 2 => (refcount=0, is_ref=0)=2)
说明:
-
赋值后,$a, $b指向同一个zend_array。引用计数为2。
-
对 b 时 行 修 改 时 , 发 生 写 时 复 制 , b时行修改时,发生写时复制, b时行修改时,发生写时复制,b复制一份新的zend_array,再对 b [ 1 ] 进 行 修 改 , 因 为 两 者 引 用 计 数 都 变 成 1 。 此 时 如 果 查 看 b[1]进行修改,因为两者引用计数都变成1。此时如果查看 b[1]进行修改,因为两者引用计数都变成1。此时如果查看a, 它的内容是没有改变的。
2. 所有变量都会发生写时复制么?
不是所有类型的变量都可以发生写时复制。
| type | copyable |
+----------------+------------+
|simple types | |
|string | Y |
|interned string | |
|array | Y |
|immutable array | |
|object | |
|resource | |
|reference | |
如上表所示,只有string和array类型的变量,会发生写时复制。
zval.u1.type_flag中记录了当前变量是否可以进行copy。
#define IS_TYPE_COPYABLE (1<<4)
3. 写时复制的启示
理解写时复制对于理解array, string, object类型的赋值修改,及做为函数参数传递后的修改特别重要。
下面我们看一个object的例子:
class Demo{
public $name = 'y';
}
$a = new Demo();
$b = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
$b->name = 'x';
xdebug_debug_zval('a');
xdebug_debug_zval('b');
输出:
a: (refcount=2, is_ref=0)=class Demo { public $name = (refcount=0, is_ref=0)='y' }
b: (refcount=2, is_ref=0)=class Demo { public $name = (refcount=0, is_ref=0)='y' }
a: (refcount=2, is_ref=0)=class Demo { public $name = (refcount=0, is_ref=0)='x' }
b: (refcount=2, is_ref=0)=class Demo { public $name = (refcount=0, is_ref=0)='x' }
说明:
- $b = $a后,两者指向是一个zend_object, 引用计数为2。
- 因为object类型不会发生写时复制,所以修改 b 的 n a m e 值 , 等 同 于 修 改 b的name值,等同于修改 b的name值,等同于修改a的name值。因此我们看到,修改后,引用计数仍然为2, a,b的name属性值都变为x。
同理,你可以得出下面一段代码的输出为"ball"。
class Demo{
public $name = 'y';
}
$a = new Demo();
$b = $a;
function change($obj){
$obj->name = "ball";
}
change($a);
echo $b->name;
对于array和string类型,因为必会发生写时复制,所以任何情况下对其内部元素的修改都不会影响原值。
结论:
类型 | 赋值后修改 | 作为函数参数修改 |
---|---|---|
object | 原值改变 | 原值改变 |
string | 原值不变 | 原值不变 |
array | 原值不变 | 原值不变 |