最近看PHP中的引用计数器部分,首先被各种绕晕,然后通过看博客和分析后,总结了一个比较通俗的解释,能帮助自己很好地记忆,也希望能帮助到各位读者。这里分享一遍博文,是比较正统的解释:PHP变量之引用(http://hilojack.sinaapp.com/?p=1392)。
研究PHP引用计数器的变化可以通过安装Xdebug扩展来学习,安装后直接调用 xdebug_debug_zval('var') 来看变量 $var的引用计数器情况。
谈引用计数器需要对PHP中变量的存储、引用计数的机制有所了解,引用计数当然是节约内存,在不影响语义正确性的前提下,让多个变量符号共享一个内存值空间(又称为变量容器)。引用计数什么时候发生变化:赋值。 赋值又有两种: 值传递赋值和引用赋值。比较难以理解的是引用赋值。引用计数的另一个作用就是指示什么时候可以共用同一空间,什么时候必须进行变量分离(另开辟空间)。
为了便于理解和记忆,本人对赋值中的各种情景给以通俗的解释,不要与现实情况对号入座,。
& 号是结合,可以相当于结婚,但是PHP中允许多个人一起结婚(即一夫多妻制或一妻多夫制),这个比较变态。 注意结婚必须同居,分居是不行的。除了结婚外还有一种形态叫 合租。合租也是同居,但是没有任何关系,允许多人合租,这个是合情合理的。当然还有一种情况是独居,这个比较容易理解。以下面的例子说明赋值语句与这三种状态对应关系:
情景一:
$a = "a"; // $a 独居, is_ref = 0, refcount = 1;
$b = $a; // $a 与 $b 合租, is_ref = 0, refcount = 2;
情景二:
$a = "a"; // $a 独居, is_ref = 0, refcount = 1;
$b = &$a; // $a 与 $b 结婚, is_ref = 1, refcount = 2;
上面可以看出, is_ref可以理解为结婚证,=1 表示两个和多个变量是结婚关系, =0没有结婚(可以合租或同居),refcount表示了多少个变量住在一起了,=1表示独居,>1表示多人同居(记住,结婚必须同居,但同居未必是结婚关系)。
下面开始分析赋值时的变量变化关系:
情景一:
$a = "a"; // $a 独居, is_ref = 0, refcount = 1;
$b = $a; // $a 与 $b 合租, is_ref = 0, refcount = 2;
$va = "b"; // $va 独居, is_ref = 0, refcount = 1;
$vb = $va; // $va 和 $vb 合租, is_ref = 0, refcount = 2;
$a = $va; // $a是单身,$va也是单身,因此 $a 搬去与 $va 同居,现在$a,$va,$vb三人同居
赋值后:
$a: is_ref = 0, refcount = 3, string = "xyz"
$b: is_ref = 0, refcount = 1, string = "qwe"
$va: is_ref = 0, refcount = 3, string "xyz"
$vb: is_ref = 0, refcount = 3, string "xyz"
$a = "a"; // $a 独居, is_ref = 0, refcount = 1;
$b = &$a; // $a 与 $b 结婚, is_ref = 1, refcount = 2;
$va = "b"; // $va 独居, is_ref = 0, refcount = 1;
$vb = $va; // $va 和 $vb 合租, is_ref = 0, refcount = 2;
$a = $va; // $a是已婚, $a 不能随便单独搬出去,赋值会使$va的值拷贝给$a,其他关系不变
$a: is_ref = 1, refcount = 2, string = "xyz"
$b: is_ref = 1, refcount = 2, string = "xyz"
$va: is_ref = 0, refcount = 2, string "xyz"
$vb: is_ref = 0, refcount = 2, string "xyz"
$a = "a"; // $a 独居, is_ref = 0, refcount = 1;
$b = &$a; // $a 与 $b 结婚, is_ref = 1, refcount = 2;
$va = "b"; // $va 独居, is_ref = 0, refcount = 1;
$vb = &$va; // $va 和 $vb 结婚, is_ref = 1, refcount = 2;
$a = $va; // $a是已婚, 与情景二相同,值拷贝,关系不变
赋值后:
$a: is_ref = 1, refcount = 2, string = "xyz"
$b: is_ref = 1, refcount = 2, string = "xyz"
$va: is_ref = 1, refcount = 2, string "xyz"
$vb: is_ref = 1, refcount = 2, string "xyz"
$a = "qwe"; // $a 独居, is_ref = 0, refcount = 1;
$b = $a; // $a 与 $b 同居, is_ref = 0, refcount = 2;
$va = "xyz"; // $va 独居, is_ref = 0, refcount = 1;
$vb = &$va; // $va 和 $vb 结婚, is_ref = 1, refcount = 2;
$a = $va; // $a 想与 $va同居(而非结婚),但是$va已婚的,因此 $a只能从$b那里搬出来,重新分配个房子,值与$va一样(术语叫:变量分离);$va和$vb关系不变
赋值后:
$a: is_ref = 0, refcount = 1, string = "xyz"
$b: is_ref = 0, refcount = 1, string = "qwe"
$va: is_ref = 1, refcount = 2, string "xyz"
$vb: is_ref = 1, refcount = 2, string "xyz"
$a = "qwe"; // $a 独居, is_ref = 0, refcount = 1;
$b = $a; // $a 与 $b 同居, is_ref = 0, refcount = 2;
$va = "xyz"; // $va 独居, is_ref = 0, refcount = 1;
$vb = $va; // $va 和 $vb 同居, is_ref = 0, refcount = 2;
$a = &$va; // $a 想与 $va结婚,现在$a 和 $va 都是单身但是都有室友了,因此他们各自从原来的地方搬出来,然后分个新房子,值与$va原来的一样
赋值后:
$a: is_ref = 1, refcount = 2, string = "xyz"
$b: is_ref = 0, refcount = 1, string = "qwe"
$va: is_ref = 1, refcount = 2, string "xyz"
$vb: is_ref = 0, refcount = 1, string "xyz"
$a = "qwe"; // $a 独居, is_ref = 0, refcount = 1;
$b = $a; // $a 与 $b 同居, is_ref = 0, refcount = 2;
$va = "xyz"; // $va 独居, is_ref = 0, refcount = 1;
$vb = &$va; // $va 和 $vb 结婚, is_ref = 1, refcount = 2;
$a = &$va; // $a 想与 $va 结婚,但是 $va 是已婚的,而 $a 是单身,因此 $a 搬过去和 $va 住,$va 现在有两个配偶:$vb 和 $a
赋值后:
$a: is_ref = 1, refcount = 3, string = "xyz"
$b: is_ref = 0, refcount = 1, string = "qwe"
$va: is_ref = 1, refcount = 3, string "xyz"
$vb: is_ref = 1, refcount = 3, string "xyz"
$a = "qwe"; // $a 独居, is_ref = 0, refcount = 1;
$b = &$a; // $a 与 $b 结婚, is_ref = 1, refcount = 2;
$va = "xyz"; // $va 独居, is_ref = 0, refcount = 1;
$vb = $va; // $va 和 $vb 同居, is_ref = 0, refcount = 2;
$a = &$va; // $a 想与 $va 结婚,但是 $a 是已婚的,$va 是单身, 解决办法是 $a 离婚后和 $va 结婚,同时 $va 从与 $vb合租的地方搬出来
赋值后:
$a: is_ref = 1, refcount = 2, string = "xyz"
$b: is_ref = 0, refcount = 1, string = "qwe"
$va: is_ref = 1, refcount = 2, string "xyz"
$vb: is_ref = 0, refcount = 1, string "xyz"
$a = "qwe"; // $a 独居, is_ref = 0, refcount = 1;
$b = &$a; // $a 与 $b 结婚, is_ref = 1, refcount = 2;
$va = "xyz"; // $va 独居, is_ref = 0, refcount = 1;
$vb = &$va; // $va 和 $vb 结婚, is_ref = 1, refcount = 2;
$a = &$va; // $a 想与 $va 结婚,但是 $a和$va都是已婚的,谁离婚?$a!,因为是$a主动想和$va结婚,
// $a 离婚后$va住一起,$va 现在有两个配偶:$vb 和 $a
赋值后:
$a: is_ref = 1, refcount = 3, string = "xyz"
$b: is_ref = 0, refcount = 1, string = "qwe"
$va: is_ref = 1, refcount = 3, string "xyz"
$vb: is_ref = 1, refcount = 3, string "xyz"
简单赋值 就是同居,需考察等号左侧变量的is_ref(即是否已婚),若 is_ref = 1,则值拷贝,否则考虑左侧便令,能否在不另外分配内存的情形下,与右侧变量共用同一存储空间(同居),此时要考察右侧是否是已婚,若是,则不能同居,变量分离,若右侧变量也是单身,则直接共用同一内存,所有同居者都遵循 COW(写时拷贝)的原则。
引用赋值 就是结合, 需先考察右侧变量的引用情况, 若 is_ref = 1,则直接 refcount ++, 出现多人结合的情况, 若右侧是非引用的(is_ref = 0),那还需考察右侧是否是独居,若是独居,则 左右两个变量共用右侧变量空间,否则右侧变量从原来的地方分离出来和左侧开辟新空间。 只要是引用赋值,那么左侧变量总是要与之前结合的或共用的变量进行分离。