<?php
$a = 'Hello World';
$b = $a;
unset($a);
?>
第一条语句执行后,PHP创建了$a这个变量,并为它申请了12B的内存来存放"hello world"这个字符串. 紧接着把$a赋给了$b,并释放掉$a;
PHP变量的名称和值在内核中是保存在两个不同的地方的,值是通过一个与名字毫无关系的zval结构来保存,而这个变量的名字a则保存在符号表里,两者之间通过指针联系着.
现在我们检查$a和$b两个变量,他们的值指向了"hello world"这个字符串在内存中的位置. zval的四个成员value、type、is_refgc、refcountgc,
当一个变量被第一次创建的时候,它对应的zval结构体的refcountgc成员的值会被初始化为1,理由很简单,因为只有这个变量自己在用它。但是当你把这个变量赋值给别的变量时,refcountgc属性便会加1变成2,因为现在有两个变量在用这个zval结构了!
这个时候当我们再用unset删除$a的时候,它删除符号表里的$a的信息,然后清理它的值部分,这时它发现$a的值对应的zval结构的refcount值是2,也就是有另外一个变量在一起用着这个zval,所以unset只需把这个zval的refcount减去1就行了!
写时复制机制
<?php
$a = 1;
$b = $a;
$b += 5;
?>
从代码逻辑来看,我们希望语句执行后$a仍然是1,而$b则需要变成6。我们知道在第二句完成后内核通过让$a和$b共享一个zval结构来达到节省内存的目的,但是现在第三句来了,这时$b的改变应该怎样在内核中实现呢? 答案非常简单,内核首先查看refcountgc属性,如果它大于1则为这个变化的变量从原zval结构中复制出一份新的专属与$b的zval来,并改变其值。
现在$b变量拥有了自己的zval,并且可以自由的修改它的值了。
Change on Write
<?php
$a = 1;
$b = &$a;
$b += 5;
?>
我们都知道$a的值也变成6了。当我们更改$b的值时,内核发现$b是$a的一个用户端引用,也就是所它可以直接改变$b对应的zval的值,而无需再为它生成一个新的不同与$a的zval。因为他知道$a和$b都想得到这次变化! 但是内核是怎么知道这一切的呢?简单的讲,它是通过zval的is_refgc成员来获取这些信息的。这个成员只有两个值,就像开关的开与关一样。它的这两个状态代表着它是否是一个用户在PHP语言中定义的引用。在第一条语句($a = 1;)执行完毕后,$a对应的zval的refcountgc等于1,is_refgc等于0;。 当第二条语句执行后($b = &$a;),refcount__gc属性向往常一样增长为2,而且is_ref__gc属性也同时变为了1! 最后,在执行第三条语句的时候,内核再次检查$b的zval以确定是否需要复制出一份新的zval结构来,这次不需要复制,因为我们刚才上面的get_var_and_separate函数其实是个简化版,并且少写了一个条件:
/ 如果这个zval在php语言中是通过引用的形式存在的,或者它的refcount小于2,则不许要复制。/
if ((varval)->is_ref || (varval)->refcount < 2) {
return *varval;
}
这一次,尽管它的refcount等于2,但是因为它的is_ref等于1,所以也不会被复制。内核会直接的修改这个zval的值
Separation Anxiety
<?php
$a = 1;
$b = $a;
$c = &$a;
?>
<?php
$a = 1;
$b = &$a;
$c = $a;
?>
已有 0 条评论