课程咨询 :17156168575 QQ:2720475033

  • 变量改变时PHP内核改变了什么?

    发布:济南php培训      来源:济南php培训      时间:2016-07-05

  • zval

    看下面的内容之前先对zval这个结构体做个了解

    typedef struct _zval_struct {
        zvalue_value value;
        zend_uint refcount;
        zend_uchar type;
        zend_uchar is_ref;
    } zval;

    zval结构体中共有4个元素,value是一个联合体,用来真正的存储zval的值,refcount用来计数该zval被多少个变量使用,type表示zval所存储的数据类型,is_ref用来标志该zval是否被引用。

    引用计数

    <?php $a = 'Hello World'; $b = $a; unset($a); ?>

    我们一起来剖析下上面这段代码:

    • $a = 'Hello World';首先这句代码被执行,内核创建一个变量,并分配12字节的内存去存储字符串'Hello World'和末尾的NULL。
    • $b = $a;接着执行这句代码,执行这句的时候内核里面发生了什么呢?

      • $a所指向的zval中的refcount进行加1操作。
      • 将变量$b指向$a所指向的zval。
        在内核中大概是这样的,其中active_symbol_table是当前的变量符号表

         {
                zval *helloval;
                MAKE_STD_ZVAL(helloval); ZVAL_STRING(helloval, "Hello World", 1); zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),
                                                    &helloval, sizeof(zval*), NULL); ZVAL_ADDREF(helloval); zend_hash_add(EG(active_symbol_table), "b", sizeof("b"),
                                                    &helloval, sizeof(zval*), NULL); }
    • unset($a);这句代码执行后,内核会将azvalrefcountb还和原来一样

    写时复制

    <?php $a = 1; $b = $a; $b += 5; ?>

    上面这段代码执行完之后,一般肯定希望$a=1,$b=6,但是如果像引用计数那样,$a$b指向相同的zval,修改$b之后$a不是也变了?
    这个具体是怎么实现的呢,我们一起来看下:

    • $a = 1;内核创建一个zval,并分配4个字节存储数字1。
    • $b = $a;这一步和引用计数中的第二步一样,将$b指向和$a相同的zval,并将zval中的引用计数值refcount加1。
    • $b += 5;关键是这一步,这一步骤发生了什么呢,怎么确保修改之后不影响$a

      • 其实Zend内核在改变zval之前都会去进行get_var_and_separete操作,如果recfount>1,就需要分离就创建新的zval返回,否则直接返回变量所指向的zval,下面看看如何分离产生新的zval。
      • 复制一个和$b所指向zval一样的zval。
      • $b所指向的zval中的refcount计数减1。
      • 初始化生成的新zval,设置refcount=1,is_ref=0。
      • $b指向新生成的zval。
      • 对新生成的zval进行操作,这就是写时复制。
        下面看看内核中分离时的主要代码:

         zval *get_var_and_separate(char *varname, int varname_len TSRMLS_DC)
            {
                zval **varval, *varcopy; if (zend_hash_find(EG(active_symbol_table),
                                varname, varname_len + 1, (void**)&varval) == FAILURE) { /* Variable doesn't actually exist  fail out */ return NULL;
            } if ((*varval)->is_ref || (*varval)->refcount < 2) { /* varname is the only actual reference,
                * or it's a full reference to other variables
                * either way: no separating to be done
                */ return *varval;
            } /* Otherwise, make a copy of the zval* value */ MAKE_STD_ZVAL(varcopy);
            varcopy = *varval; /* Duplicate any allocated structures within the zval* */ zval_copy_ctor(varcopy); /* Remove the old version of varname
            * This will decrease the refcount of varval in the process
            */ zend_hash_del(EG(active_symbol_table), varname, varname_len + 1); /* Initialize the reference count of the
            * newly created value and attach it to
            * the varname variable
            */ varcopy->refcount = 1;
            varcopy->is_ref = 0;
            zend_hash_add(EG(active_symbol_table), varname, varname_len + 1, &varcopy, sizeof(zval*), NULL); /* Return the new zval* */ return varcopy;
            }

    写时改变

    <?php $a = 1; $b = &$a; $b += 5; ?>

    上面这段代码执行完之后一般希望是:$a == $b == 6。这个又是怎么实现的呢?

    • $a = 1;这一步骤和写时复制中的第一步一样。
    • $b = &$a;这一步骤内核会将$b指向$a所指向的zval,将zval中的refcount加1,并将zval中的is_ref置为1。
    • $b += 5;这一步骤和写时复制中的第三步骤一样,但是内核中发生的事情却不一样。

      • 内核看到$b进行变化的时候,也会执行get_var_and_separate函数,看是否需要分离。
      • 如果(*varval)->is_ref的话也会直接返回$b所指向的zval,不去分离产生新的zval,不管zval的refcount是否>1。
      • 这时候再去修改$b值,$a的值也就改变了,因为他们指向相同的zval。

    分离的问题

    说道现在聪明的你可能已经看出点问题了,如果一个zval结构体既有refcount计数又有is_ref引用这个时候怎么办?

    <?php $a = 1; $b = $a; $c = &$a; ?>

    如果出现上面这种情况的时候,如果$a、$b、$c指向同一个zval结构体,进行改变的时候Zend到底去听谁的?其实这个地方不会指向同一个zval了。
    如果对一个is_ref = 0 && refcount >1的zval进行写时改变这种赋值形式(就是引用赋值)的时候,Zend会将等号右边的变量分离出来一个新的zval,
    对这个zval进行初始化,对之前的zval的refcount进行减1操作,让等号左边的变量指向这个新的zval,refcount进行加1操作,is_ref=1。看看下面这张图片

    #FormatImgID_0#

    <?php $a = 1; $b = &$a; $c = $a; ?>

    上面这又是另外一种情况,在is_ref = 1的情况下,试图单纯的进行refcount+1操作的时候会分离出来一个新的zval给等号左边的变量,并初始化他,看看下面这张图片

    #FormatImgID_1#

    参考文献

    1.《Extending and Embedding PHP》- Chaper 3 - Memory Management.


上一篇:重构的七大误区!

下一篇:ANGULARJS 2.0以及前后端边界

最新开班日期  |  更多

PHP高级开发工程师就业班

PHP高级开发工程师就业班

开班日期:9月28日

PHP高级开发工程师周末班

PHP高级开发工程师周末班

开班日期:9月28日

济南PHP培训班就业班

济南PHP培训班就业班

开班日期:9月28日

PHP高级开发工程师周末班

PHP高级开发工程师周末班

开班日期:9月28日

 扫一扫,关注一下! 济南:历下区山大路47号数码港大厦 济南:历下区趵突泉北路三联商社
青岛:市南区金坛路17号 潍坊:奎文区东风东街299号建行大厦
烟台:海港路25号阳光100城市广场 临沂:兰山区红旗路1号苏宁易购
淄博:张店区金晶大道68号华润大厦 济宁:市中区太白路10号苏宁生活广场
课程培训电话:17156168575 QQ:2720475033 全国服务监督电话:400-111-8989    服务邮箱 tousu@tedu.cn

2001-2017 达内时代科技集团有限公司 版权所有 京ICP证8000853号-56