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都要实例化一个结构体出来,像上面这种情况,其实a与b是相等,但如果还要实例化两个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的值都发生了改变。