对码当歌,猿生几何?

php引用与写时复制

引用是什么?

与C语言中的指针不同,C语言中是内存中的地址,而PHP中是符号表中的变量(也叫符号)的名字,意味着可以使用不同的名字访问同一个zval变量容器,类似于unix系统的硬链接的概念。例如,下面的两个变量是指向zval变量容器,一个变量改变另一个也会改变。

$a = 'first';
$b = & $a;
echo $a ;        // first
echo $b ;        // first
$b = 'secondary';
echo "------------";
echo $a ;        // secondary
echo $b ;        // secondary

写时复制

对于使用一个变量的值给另一个变量赋值,传统意义上是变量值内存的复制,意味需要申请与原来变量一样大小的内存空间,这种做法非常浪费内存空间。PHP内核对于变量的赋值采取一种非常巧妙的机制——写时复制。那么什么是写时复制呢,请看下面例子。

$var = "first";
xdebug_debug_zval('var');
$var1 =  $var;
xdebug_debug_zval('var');
$var2 =  $var;
xdebug_debug_zval('var1');
$var2 = 'secondary';
xdebug_debug_zval('var2');
xdebug_debug_zval('var');
$var3 = $var;
xdebug_debug_zval('var');
xdebug_debug_zval('var1');
xdebug_debug_zval('var2');
xdebug_debug_zval('var3');

输出:
var: (refcount=1, is_ref=0)='first'
var: (refcount=2, is_ref=0)='first'
var1: (refcount=3, is_ref=0)='first'
var2: (refcount=1, is_ref=0)='secondary'
var: (refcount=2, is_ref=0)='first'
var: (refcount=3, is_ref=0)='first'
var1: (refcount=3, is_ref=0)='first'
var2: (refcount=1, is_ref=0)='secondary'
var3: (refcount=3, is_ref=0)='first'

代码分析:

1、一开始$var的refcount为1,在赋值给$var1后refcount成为了2,说明$var1和$var是指向的同一个zval变量容器

2、$var在赋值给$var2后, 发现$var1的refcount成为了3,其实这时候$var、$var1和$var2的refcount的值都是3,更加说明用一个变量给另一个变量赋值,其实就是引用,都指向同一个zval变量容器

3、紧接着将$var2赋值为secondary,发现,$var2的refcount为1,$var的refcount为2。这是为什么呢?这时候就体现出了写时复制的作用,由于$var2被赋予了新的值,PHP内核需要申请新的内存空间来存储,而此时$var2不再表示是$var指向的变量,所以$var的refcount减1。

4、再将$var赋给$var3, 此时$var、$var1和$var3的refcount都为3,$var2的refcount仍然为1。

总结一下:

写时复制的机制,就是多个指向同一内存空间的变量,当其中一个变量被赋予其他值的情况下,PHP内核才会去申请新的内存空间存储新值。

其实,写时复制也是一种特殊的引用,只不过这个原有引用关系会在有变量被赋予新值的情况下被破坏掉。

每个php变量存在一个叫"zval"的变量容器中