php深入学习之变量的引用计数

本文深入探讨PHP中变量的引用计数机制及其工作原理,包括如何通过引用计数节省内存资源,以及写时复制机制如何避免不必要的内存分配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

php变量的引用计数是什么呢?首先看一下php变量的底层结构:

typedef struct _zval_struct zval;
struct _zval_struct {
    zvalue_value value;     //存放value
    zend_uint refcount__gc;     //引用计数
    zend_uchar type;     //判断类型
    zend_uchar is_ref__gc;     //是否被引用
};

当我们在php程序中声明一个变量,zend引擎会实例化一个zval的结构体,这个结构体有四个成员变量。其中type用于判断这个变量是什么类型的;value用于存放实际的数据,其实value也是一个联合体,具体这里就不细讲了。今天重点讲一下:refcount_gc和is_ref_gc这两个字段的用处。假设有这样一段代码:

$a = 123;
$b = $a;

由于一个简单的int型变量,php都要实例化一个结构体出来,像上面这种情况,其实ab是相等,但如果还要实例化两个zval结构体的话,感觉有点浪费内存的。所以php开发者想出了一个方法—-“引用计数,写时复制”。
就是说,当执行代码 b=a;时,并没有再实例化出一个zval结构体,而是把b变量的指针指向a变量的zval,然后把a的zval的refcount__gc值加上一。如图所示:
这里写图片描述
但当执行如下代码:$b = 345; 就变成了如下情况:
这里写图片描述
当要修改变量b的值时,zend引擎找到b所对应的zval,这时候发现该zval里面的refcount_gc不为1,然后就复制一个新的zval,并把b的指针指向这个新的zval,并把原来的zval的refcount_gc的值减一。这就是写时复制。
再看如下代码:

$a = 123;
$b = &$a;

这时候的情况是这样的:
这里写图片描述

可以看到,a、b变量所指的zval的is_ref_gc的值变为1。那么,这之后如果再执行代码

$b = 345;

zend引擎找到b变量对应的zval,发现虽然refcount_gc=2,但是is_ref_gc=1,所以,他就不会复制出一个新的zval,而是直接修改这个zval的值。
如果后续还有这样一条代码:

$c = $a;

这是zend引擎找到a变量对应的zval,发现这里的is_ref_gc=1,但是这里的c并不是引用a,所以就会直接复制出一个zval给c,而不是让c先暂时的指向a的zval。
在php中,可以使用xdebug_debug_zval这个函数来查看一个zval的refcount_gc和is_ref_gc的值。比如有如下代码:

$a = 123;
$b = &$a;
$c = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');

执行之后会得到如下结构:

a:(refcount=2, is_ref=1),int 123
b:(refcount=2, is_ref=1),int 123
c:(refcount=1, is_ref=0),int 123

现在再说一下数组的refcount_gc和is_ref_gc的值到底是什么样的呢。比如我们有如下代码:

$blakeFez = array(
     'name' => 'blakeFez',
     'age' => 90
);
$new = $blakeFez;
$new['age'] = 91;

我们来分析一下这个过程。首先,我们会初始化一个数组blakeFez,这个数组有两个元素name和age,这时,zend引擎会实例出三个zval。结构如图所示:

这里写图片描述

然后代买执行

$new = $blakeFez;

这时候结构如下:

这里写图片描述

当执行

$new['age'] = 91;

时,zend引擎会首先找到$new对应的那个zval,这时发现当前zval的refcount_gc=2,所以就会复制出一份。这时结构如下:
这里写图片描述

接着,zend引擎再找到$new[‘age’]对应的那个zval,这时候发现当前的zval的refcount_gc=2,所以也复制出一份。所以最终结果就是这个样子:
这里写图片描述

那现在我们做一个比较有趣的事情。把上面的那段代码改一下:


$blakeFez = array(
     'name' => 'blakeFez',
     'age' => 90
);
$temp = &$blakeFez['age'];//注意这行代码
$new = $blakeFez;
$new['age'] = 91;
echo $blakeFez['age'],"\n";
echo $new['age'],"\n";

这时候的$blakeFez[‘age’]跟$new[‘age’]会是什么样子的呢?
我们来分析一下这个过程。当程序运行


$blakeFez = array(
     'name' => 'blakeFez',
     'age' => 90
);
$temp = &$blakeFez['age'];

这两行代码后,底层的结构是这样的。

这里写图片描述

之后再执行

$new = $blakeFez;

这时,zend找到blakeFez的zval,发现它的is_ref_gc=0,refcount_gc=1,所以会让$new直接指向改zval,所以底层结构变成如下样子:
这里写图片描述

接在再执行

$new['age'] = 91;

首先zend引擎找到$new指向的zval,发现该zval的refcount_gc=2,is_ref_gc=0,所以就会复制一个zval。此时底层结构如下:
这里写图片描述

然后zend再找到$new[‘age’]所指的zval,这时发现该zval的is_ref_gc=1,所以就直接在该zval上修改值,这就导致了$blakeFez[‘age’]和$temp的值都发生了改变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值