一、回顾变量结构
在了解php内核是如何进行变量分离和引用的时候,我们要提前了解变量的结构体,如下
zval是_zval_struct结构体的别名
typedef struct _zval_struct zval;
_zval_struct 结构体
struct _zval_struct {
/* Variable information */
zvalue_value value; /* 变量值保存在这里 12字节*/
zend_uint refcount;//4字节,变量引用计数器
zend_uchar type; /* active type变量类型 1字节*/
zend_uchar is_ref;//是否变量被&引用,0表示非引用,1表示引用,1字节
};
_zvalue_value结构体
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val; //4字节
int len; //4字节
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
} zvalue_value;
二、refcount和is_ref
这两个字段对于php的变量分离和引用,起着至关重要的作用,也包括了php的内存回收,下面我们就围绕这两个字段进行学习
is_ref 的意思,就是标记变量是否为引用变量。如果是普通变量则为0,引用变量则为1
refcount 记录了当前zval被引用的次数
三、实例
接下来我们来看一段简单的代码
<?php
$age = "yilian";
$num = $age;
?>
正如上面所说,像这么段代码,
1、首先创建了字符变量,申请了7个字节的内存,分别保存 yilian 和 NULL(\0)的结尾
2、第二行重新定义一个新字符,并"复制"了变量age的值给变量num
分析:
以上这个,php是在symbol_table中存储了一个值age,对应这个指针是指向一个zval结构体,变量值"yilian",就存在这个zval中,上面这个因为,age和num,是同一个值,其实并非是申请了两个空间,而是通过指针指向同一个zval来实现的。避免了内存空间的浪费。
<?php
$age = "yilian";//创建了个age变量。此时保存这个zval的refcount为1
debug_zval_dump($age);
$num = $age;//创建了个新的变量,变量指向刚才创建的age,把zval的refcount加1,变为2
debug_zval_dump($age);
?>
以上这段代码打印结果如下

分析:
可能你会发现,以上打印结果和代码里面的分析的refcount的值不同,这是因为debug_zval_dump导致的,这其中也涉及到了变量的传参,导致传入到debug_zval_dump的时候,又加了一次1,所以是 2和3
<?php
$age = "yilian";
$num = $age;
debug_zval_dump($num);
?>
和
<?php
$age = "yilian";
$num = $age;
unset($age);
debug_zval_dump($num);
?>
以上打印,分别输出如下


通过以上可知道,当unset的时候,zval被引用的次数将会被减少1
<?php
$age = "yilian";
$num = $age;
$age = 1;
debug_zval_dump($num);
?>
这个打印效果如下

这和上面相比,refcount也减少了1
分析:
这个就是写时复制机制
php在修改一个变量前,会先查看这个变量的引用计数,也就是refcount的值,是否大于1,如果是大于1,就会执行一个分离的过程,如上,php执行赋值1的时候,,查看到refcount大于1,就会复制一个新的zval出来。并且把原先的val的refcount减1,并修改symbol_table,age就指向这个新的zval结构,num指向原来的zval结构。
<?php
$age = "yilian";
$num = &$age;
$age = 1;
?>
以上这段将导致num的值也变为1,
分析:
当执行到第二行的时候,$age对应的zval的refcount变为2,并且把is_ref置为1,
第三行的时候,会判断is_ref是否为1 如果是1则不分离,所以age和num还是同一个zval,
<?php
$age = "yilian";
$num = $age;//refcount为2
$sex = &$age;//因为refcount大于1,执行分离,并把refcount减1,然后将age和sex关联
?>
所以以上这个age和sex的refcount为1
逻辑类似如下
if((*val)->is_ref || (*val)->refcount<2){
//不执行分离
... ;//process
}
<?php
$age = "yilian";//refcount为1
$num = &$age;//不分离,refcount加1,把is_ref置为1,并把age和num代表的zval进行关联
debug_zval_dump($age);
?>
不过有个奇怪的地方,这里打印出来的age,refcount为1,这是为什么呢?
分析:
因为当执行debug_zval_dump(age会以传值得方式传递给debug_zval_dump,此参数不是引用传递,是引用变量分离,所以debug_zval_dump收到的参数age已经和原来的age、num进行分离了,所以变成了1
网友评论