php内核分析:变量分离/引用

一、回顾变量结构

在了解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被引用的次数

三、实例

接下来我们来看一段简单的代码


正如上面所说,像这么段代码,
1、首先创建了字符变量,申请了7个字节的内存,分别保存 yilian 和 NULL(\0)的结尾
2、第二行重新定义一个新字符,并"复制"了变量age的值给变量num
分析:
以上这个,php是在symbol_table中存储了一个值age,对应这个指针是指向一个zval结构体,变量值"yilian",就存在这个zval中,上面这个因为,age和num,是同一个值,其实并非是申请了两个空间,而是通过指针指向同一个zval来实现的。避免了内存空间的浪费。


以上这段代码打印结果如下


php内核分析:变量分离/引用_第1张图片
image.png

分析:
可能你会发现,以上打印结果和代码里面的分析的refcount的值不同,这是因为debug_zval_dump导致的,这其中也涉及到了变量的传参,导致传入到debug_zval_dump的时候,又加了一次1,所以是 2和3



以上打印,分别输出如下


php内核分析:变量分离/引用_第2张图片
image.png
php内核分析:变量分离/引用_第3张图片
image.png

通过以上可知道,当unset的时候,zval被引用的次数将会被减少1


这个打印效果如下


php内核分析:变量分离/引用_第4张图片
image.png

这和上面相比,refcount也减少了1
分析:
这个就是写时复制机制
php在修改一个变量前,会先查看这个变量是否有被其他引用,也就是refcount的值,是否大于1,如果是大于1,就会执行一个分离的过程,如上,php执行赋值1的时候,,查看到refcount大于1,就会复制一个新的zval出来。并且把原先的val的refcount减1,并修改symbol_table。


以上这段将导致num的值也变为1,
分析:
当执行到第二行的时候,$age对应的zval的refcount变为2,并且把is_ref置为1,
第三行的时候,会判断is_ref是否为1 如果是1则不分离,所以age和num还是同一个zval,


所以以上这个age和sex的refcount为1
逻辑类似如下

if((*val)->is_ref || (*val)->refcount<2){
          //不执行分离
        ... ;//process
  }

不过有个奇怪的地方,这里打印出来的age,refcount为1,这是为什么呢?

分析:
因为当执行debug_zval_dump(age会以传值得方式传递给debug_zval_dump,此参数不是引用传递,是引用变量分离,所以debug_zval_dump收到的参数age已经和原来的age、num进行分离了,所以变成了1

你可能感兴趣的:(php内核分析:变量分离/引用)