php不要随便复制变量,PHP变量写时复制和写时改变的理解

// PHP 变量的 写时复制(cow) 和 写时改变(cow) 笔记

// since: 2014-12-15

// liuzhengyi

// ref:

zval简介

PHP是一个弱类型的语言,所有的数据类型,在PHP内部都用zval这个数据结构表示.

zval的结构如下:

typedef struct _zval_struct {

zvalue_value value;     // 存储真实的数据值, 是一个结构体

zvalue_uint ref_count;  // 引用计数

zvalue_uchar type;      // 变量类型

zvalue_uchar is_ref;    // 是否是一个引用变量(&)

} zval;

debug_zval_dump() 方法可以dump出变量的一些zval信息.如:

$a = 'hi';

debug_zval_dump($a);

// string(2) "hi" refcount(2)

这里输出的refcount就是该变量的zval的引用计数.

PHP引擎为了节约内存使用,会尽量重用zval:

当执行 $b = $a; 时,

PHP并不会重新分配内存,生成一个新的zval. 而是会在符号表中增加一条记录,让$b和$a共用一个zval.

同时将这个zval的引用计数增加1.

当执行 $b=&$a; 时,$b和$a也是共用一个zval,引用计数也会增加.

但是$b和$a互为引用(使用同一个zval,并且值联动改变),为了记录这个信息,zval的is_ref会置为1.

注意, 当 $b = $a时, 两者的值不会联动改变.

而当 $b = &$a 时, 两者的值会联动改变.

PHP是通过判断 zval中的ref_count和is_ref的值来做到这一点的.

写时复制

考虑如下代码:

$a = 'hi';

$b = $a;

$b = 'hello';

第一行将生成一个zval,存储内容为'hi', ref_count=1, is_ref=0.

第二行不会生成新的zval, $b和$a共用一个zval, 同时zval的 ref_count自增1, is_ref不变(为0).

第三行的时候,会发生什么呢?

在对$b进行写(赋值)时,PHP检查其zval的ref_count和is_ref.

当发现其ref_count>1,is_ref=0时,就知道了$b和其他变量共用一个zval,并且其值*不应该*和共用者联动变化.

于是,PHP会新生成一个zval,存储$b的值"hello".

$b之前和$a共用的zval,现在不在和$b有任何关系了.

我们就说$b从之前的zval中分离出来了,之前的zval的ref_count会自减1.

这就是写时复制,当对共用zval的非引用变量进行赋值(写)时,PHP会复制一份zval出来.

写时改变

考虑如下代码:

$a = 'hi';

$b = &$a;

$b = 'hello';

第一行将生成一个zval,存储内容为'hi', ref_count=1, is_ref=0.

第二行不会生成新的zval, $b和$a共用一个zval, 同时zval的 ref_count自增1, is_ref也*置为1*.

第三行的时候,对$b重新赋值,$a的值也应该改变,这又是如何做到的呢?

同样, 在对$b进行写(赋值)时,PHP检查其zval的ref_count和is_ref.

当发现其ref_count>1,is_ref=1时,就知道了$b和其他变量共用一个zval,并且其值*应该*和共用者联动变化.

于是,PHP不会对$b进行分离,而是直接对应zval的值,于是共用这个zval的$a的内容也发生了变化.

此即为写时改变.

两种复合情况

考虑如下代码:

$a = 'hi';

$b = &$a;

$c = $b;

第一行将生成一个zval,存储内容为'hi', ref_count=1, is_ref=0.

第二行不会生成新的zval, $b和$a共用一个zval, 同时zval的 ref_count自增1, is_ref也*置为1*.

第三行的时候,将$b赋值给$c.此时会发生什么?

$c 获得 $b的值的拷贝,但不会和$a,$b建立引用关系.

所以无法和$b共用zval(如果共用zval,则无法表示$a和$b互为引用,而和$c不是引用关系).

于是,会新生成一个zval,供$c使用,ref_count=1, is_ref=0.

而$a 和 $b 还是共用之前的zval.

再考虑如下代码:

$a = 'hi';

$b = $a;

$c = &$b;

第一行将生成一个zval,存储内容为'hi', ref_count=1, is_ref=0.

第二行不会生成新的zval, $b和$a共用一个zval, 同时zval的 ref_count自增1, is_ref不变,为0.

第三行的时候,将$c设置为$b的引用.此时又会发生什么?

$b和$c互为引用,而和$a没有引用关系,一个zval无法表示,还是需要新生成一个zval.

本来$a和$b共用一个zval,现在$b和$c要互为引用,必须共用一个zval,所以需要将$b和$a分离开.

所以,这里先将$b和$a分离,生成一个新的zval,然后将$b和$c绑定到一个zval上,并将is_ref设为1.

ref_count和is_ref的另外一种情况

前面说的几种情况都能对应到:

ref_count>1 && is_ref=0

ref_count>1 && is_ref=1

剩下的情况呢,就是 ref_count<2.

ref_count<2,说明这个变量独立使用一个zval,修改时不需要考虑其他变量.

其实就是这种情况:

$a = 'hi';

$a = 'hello';

没有分离,也没有复制.

debug_zval_dump()的输出

前面提到:

$a = 'hi';

debug_zval_dump($a);

会输出 ref_count = 2.

但是当执行:

$b = &$a;

debug_zval_dump($b);

则会输出 ref_count = 1.

这是为什么呢?

PHP中简单变量作为函数参数传递时,是按值传递的,即会传递变量的一个复制.

所以将一个独占zval的变量传递给debug_zval_dump()时,传参时的复制操作使该zval的ref_count增加了1.

而将一个共用引用zval(is_ref=1)的变量传递给debug_zval_dump()时,传参时的复制操作导致变量分离.

最终导致传入的变量的zval的ref_count=1.

笔记一篇,欢迎批评讨论!

阅读(1483) | 评论(1) | 转发(0) |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值