Heim  >  Artikel  >  php教程  >  php中的foreach问题

php中的foreach问题

WBOY
WBOYOriginal
2016-06-13 11:29:26956Durchsuche

前言:

php4中引入了foreach结构,这是一种遍历数组的简单方式。相比传统的for循环,foreach能够更加便捷的获取键值对。在php5之前,foreach仅能用于数组;php5之后,利用foreach还能遍历对象(详见:遍历对象)。本文中仅讨论遍历数组的情况。

foreach虽然简单,不过它可能会出现一些意外的行为,特别是代码涉及引用的情况下。

下面列举了几种case,有助于我们进一步认清foreach的本质。

问题1:

$arr = array(<span 1</span>,<span 2</span>,<span 3</span><span );

</span><span foreach</span>($arr <span as</span> $k => &<span $v) {
    $v </span>= $v * <span 2</span><span ;
}
</span><span //</span><span  now $arr is array(2, 4, 6)</span>

<span foreach</span>($arr <span as</span> $k =><span  $v) {
    echo </span><span "</span><span $k</span><span "</span>, <span "</span><span  => </span><span "</span>, <span "</span><span $v</span><span "</span><span ;
}</span>

先从简单的开始,如果我们尝试运行上述代码,就会发现最后输出为0=>2  1=>4  2=>4

为何不是0=>2  1=>4  2=>6 ?

其实,我们可以认为 ( => 隐含了如下操作,分别将数组当前的'键'和当前的'值'赋给变量$k和$v。具体展开形如:

<span foreach</span>(<span $arr</span> <span as</span> <span $k</span> => <span $v</span><span ){ <br /></span><span     //在用户代码执行之前</span><span 隐含了2个赋值操作<br /></span><span     $v</span> =<span  currentVal(); <br />    </span><span $k</span> =<span  currentKey();<br />
    </span><span //继续运行</span><span 用户代码</span>
<span     &hellip;&hellip;
}</span>

根据上述理论,现在我们重新来分析下第一个foreach:

第1遍循环,由于$v是一个引用,因此$v = &$arr[0],$v=$v*2相当于$arr[0]*2,因此$arr变成2,2,3

第2遍循环,$v = &$arr[1],$arr变成2,4,3

第3遍循环,$v = &$arr[2],$arr变成2,4,6

随后代码进入了第二个foreach:

第1遍循环,隐含操作$v=$arr[0]被触发,由于此时$v仍然是$arr[2]的引用,即相当于$arr[2]=$arr[0],$arr变成2,4,2

第2遍循环,$v=$arr[1],即$arr[2]=$arr[1],$arr变成2,4,4

第3遍循环,$v=$arr[2],即$arr[2]=$arr[2],$arr变成2,4,4

OK,分析完毕。

如何解决类似问题呢?php手册上有一段提醒:

Warning : 数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用unset()来将其销毁。
<span $arr</span> = <span array</span>(1,2,3<span );

</span><span foreach</span>(<span $arr</span> <span as</span> <span $k</span> => &<span $v</span><span ) {
    </span><span $v</span> = <span $v</span> * 2<span ;
}
</span><span unset</span>(<span $v</span><span );

</span><span foreach</span>(<span $arr</span> <span as</span> <span $k</span> => <span $v</span><span ) {
    </span><span echo</span> "<span $k</span>", " => ", "<span $v</span>"<span ;
}
</span><span //</span><span  输出 0=>2  1=>4  2=>6</span>

从这个问题中我们可以看出,引用很有可能会伴随副作用。如果不希望无意识的修改导致数组内容变更,最好及时unset掉这些引用。

问题2:

<span $arr</span> = <span array</span>('a','b','c'<span );

</span><span foreach</span>(<span $arr</span> <span as</span> <span $k</span> => <span $v</span><span ) {
    </span><span echo</span> <span key</span>(<span $arr</span>), "=>", <span current</span>(<span $arr</span>)<span ;
}<br /><br />// 打印 1=>b 1=>b 1=>b</span>

这个问题更加诡异。按照手册的说法,key和current分别是取数组中当前元素的的键值。

那为何key($arr)一直是1,current($arr)一直是b呢?

先用vld查看编译之后的opcode:

我们从第3行的ASSIGN指令看起,它代表将array('a','b','c')赋值给$arr。

由于$arr为CV,array('a','b','c')为TMP,因此ASSIGN指令找到实际执行的函数为ZEND_ASSIGN_SPEC_CV_TMP_HANDLER。这里需要特别指出,CV是PHP5.1之后才增加的一种变量cache,它采用数组的形式来保存zval**,被cache住的变量再次使用时无需去查找active符号表,而是直接去CV数组中获取,由于数组访问速度远超hash表,因而可以提高效率。

<span static</span> <span int</span><span  ZEND_FASTCALL  ZEND_ASSIGN_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op </span>*opline =<span  EX(opline);
    zend_free_op free_op2;
    zval </span>*value = _get_zval_ptr_tmp(&opline->op2, EX(Ts), &<span free_op2 TSRMLS_CC);
    
    </span><span //</span><span  CV数组中创建出$arr**指针</span>
    zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline-><span op1, EX(Ts), BP_VAR_W TSRMLS_CC);

    </span><span if</span> (IS_CV == IS_VAR && !<span variable_ptr_ptr) {
        &hellip;&hellip;
    }
    </span><span else</span><span  {
        </span><span //</span><span  将array赋值给$arr</span>
         value = zend_assign_to_variable(variable_ptr_ptr, value, <span 1</span><span  TSRMLS_CC);
        </span><span if</span> (!RETURN_VALUE_UNUSED(&opline-><span result)) {
            AI_SET_PTR(EX_T(opline</span>->result.u.<span var</span>).<span var</span><span , value);
            PZVAL_LOCK(value);
        }
    }

    ZEND_VM_NEXT_OPCODE();
}</span>

ASSIGN指令完成之后,CV数组中被加入zval**指针,指针指向实际的array,这表示$arr已经被CV缓存了起来。

接下来执行数组的循环操作,我们来看FE_RESET指令,它对应的执行函数为ZEND_FE_RESET_SPEC_CV_HANDLER:

<span static</span> <span int</span><span  ZEND_FASTCALL  ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    &hellip;&hellip;
    </span><span if</span><span  (&hellip;&hellip;) {
        &hellip;&hellip;
    } </span><span else</span><span  {
        </span><span //</span><span  通过CV数组获取指向array的指针</span>
        array_ptr = _get_zval_ptr_cv(&opline-><span op1, EX(Ts), BP_VAR_R TSRMLS_CC);
        &hellip;&hellip;
    }
    &hellip;&hellip;
    </span><span //</span><span  将<span 指向</span>array的指针保存到zend_execute_data->Ts中(Ts用于存放代码执行期的temp_variable)</span>
    AI_SET_PTR(EX_T(opline->result.u.<span var</span>).<span var</span><span , array_ptr);
    PZVAL_LOCK(array_ptr);

    </span><span if</span><span  (iter) {
        &hellip;&hellip;
    } </span><span else</span> <span if</span> ((fe_ht = HASH_OF(array_ptr)) !=<span  NULL) {
        </span><span //</span><span  重置数组内部指针</span>
<span         zend_hash_internal_pointer_reset(fe_ht);
        </span><span if</span><span  (ce) {
            &hellip;&hellip;
        }
        is_empty </span>= zend_hash_has_more_elements(fe_ht) !=<span  SUCCESS;
        
        </span><span //</span><span  设置EX_T(opline->result.u.var).fe.fe_pos用于保存数组内部指针</span>
        zend_hash_get_pointer(fe_ht, &EX_T(opline->result.u.<span var</span><span ).fe.fe_pos);
    } </span><span else</span><span  {
        &hellip;&hellip;
    }
    &hellip;&hellip;
}</span>

这里主要将2个重要的指针存入了Ts中:

  • EX_T(opline->result.u.var).var ---- 指向array的指针
  • EX_T(opline->result.u.var).fe.fe_pos ---- 指向array内部元素的指针

FE_RESET指令执行完毕之后,内存中实际情况如下:

接下来我们继续查看FE_FETCH,它对应的执行函数为ZEND_FE_FETCH_SPEC_VAR_HANDLER:

<span static</span> <span int</span><span  ZEND_FASTCALL  ZEND_FE_FETCH_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op </span>*opline =<span  EX(opline);
    
    </span><span //</span><span  注意指针是从EX_T(opline->op1.u.var).var.ptr获取的</span>
    zval *array = EX_T(opline->op1.u.<span var</span>).<span var</span><span .ptr;
    &hellip;&hellip;
   
    </span><span switch</span> (zend_iterator_unwrap(array, &<span iter TSRMLS_CC)) {
        </span><span default</span><span :
        </span><span case</span><span  ZEND_ITER_INVALID:
            &hellip;&hellip;

        </span><span case</span><span  ZEND_ITER_PLAIN_OBJECT: {
            &hellip;&hellip;
        }

        </span><span case</span><span  ZEND_ITER_PLAIN_ARRAY:
            fe_ht </span>=<span  HASH_OF(array);
            
            </span><span //</span><span  特别注意:
            </span><span //</span><span  FE_RESET指令中将数组内部元素的指针保存在EX_T(opline->op1.u.var).fe.fe_pos
            </span><span //</span><span  此处获取该指针</span>
            zend_hash_set_pointer(fe_ht, &EX_T(opline->op1.u.<span var</span><span ).fe.fe_pos);
            
            </span><span //</span><span  获取元素的值</span>
            <span if</span> (zend_hash_get_current_data(fe_ht, (<span void</span> **) &value)==<span FAILURE) {
                ZEND_VM_JMP(EX(op_array)</span>->opcodes+opline-><span op2.u.opline_num);
            }
            </span><span if</span><span  (use_key) {
                key_type </span>= zend_hash_get_current_key_ex(fe_ht, &str_key, &str_key_len, &int_key, <span 1</span><span , NULL);
            }
            
            </span><span //</span><span  数组内部指针移动到下一个元素</span>
<span             zend_hash_move_forward(fe_ht);
            
            </span><span //</span><span  移动之后的指针保存到EX_T(opline->op1.u.var).fe.fe_pos</span>
            zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.u.<span var</span><span ).fe.fe_pos);
            </span><span break</span><span ;

        </span><span case</span><span  ZEND_ITER_OBJECT:
            &hellip;&hellip;
    }
    
    &hellip;&hellip;
}</span>

根据FE_FETCH的实现,我们大致上明白了foreach($arr as $k => $v)所做的事情。它会根据Ts的指针去获取数组元素,在获取成功之后,将该指针移动到下一个位置再重新保存。

简单来说,由于第一遍循环中FE_FETCH中已经将数组的内部指针移动到了第二个元素,所以在foreach内部调用key($arr)和current($arr)时,实际上获取的便是1和'b'。

那为何会输出3遍1=>b呢?

我们继续看第9行和第13行的SEND_REF指令,它表示将$arr参数压栈。紧接着一般会使用DO_FCALL指令去调用key和current函数。PHP并非被编译成本地机器码,因此php采用这样的opcode指令去模拟实际CPU和内存的工作方式。

查阅PHP源码中的SEND_REF:

<span static</span> <span int</span><span  ZEND_FASTCALL  ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    &hellip;&hellip;<br /></span><span     //</span><span  从CV中获取$arr指针的指针</span>
    varptr_ptr = _get_zval_ptr_ptr_cv(&opline-><span op1, EX(Ts), BP_VAR_W TSRMLS_CC);
    &hellip;&hellip;
    
    </span><span //</span><span  变量分离,此处重新copy了一份array专门用于key函数</span>
<span     SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);
    varptr </span>= *<span varptr_ptr;
    Z_ADDREF_P(varptr);
    
    </span><span //</span><span  压栈</span>
<span     zend_vm_stack_push(varptr TSRMLS_CC);

    ZEND_VM_NEXT_OPCODE();
}</span>

上述代码中的

<span #define</span> SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv)    \
    <span if</span> (!PZVAL_IS_REF(*<span ppzv)) {                \
        SEPARATE_ZVAL(ppzv);                \
        Z_SET_ISREF_PP((ppzv));                \
    }</span>

的主要作用为,如果变量不是一个引用,则在内存中copy出一份新的。本例中它将array('a','b','c')复制了一份。因此变量分离之后的内存为:

注意,变量分离完成之后,CV数组中的指针指向了新copy出来的数据,而通过Ts中的指针则依然可以获取旧的数据。

接下来的循环就不一一赘述了,结合上图来说:

  • foreach结构使用的是下方蓝色的array,会依次遍历a,b,c
  • key、current使用的是上方黄色的array,它的内部指针永远指向b

至此我们明白了为何key和current一直返回array的第二个元素,由于没有外部代码作用于copy出来的array,它的内部指针便永远不会移动。

问题3:

<span $arr</span> = <span array</span>('a','b','c'<span );

</span><span foreach</span>(<span $arr</span> <span as</span> <span $k</span> => &<span $v</span><span ) {
    </span><span echo</span> <span key</span>(<span $arr</span>), '=>', <span current</span>(<span $arr</span>)<span ;
}<br /></span>
<span // 打印 1=>b 2=>c =></span>

本题与问题2仅有一点区别:本题中的foreach使用了引用。用VLD查看本题,发现与问题2代码编译出来的opcode一样。因此我们采用问题2的跟踪方法,逐步查看opcode对应的实现。

首先foreach会调用FE_RESET:

<span static</span> <span int</span><span  ZEND_FASTCALL  ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    &hellip;&hellip;
    </span><span if</span> (opline->extended_value &<span  ZEND_FE_RESET_VARIABLE) {
        </span><span //</span><span  从CV中获取变量</span>
        array_ptr_ptr = _get_zval_ptr_ptr_cv(&opline-><span op1, EX(Ts), BP_VAR_R TSRMLS_CC);
        </span><span if</span> (array_ptr_ptr == NULL || array_ptr_ptr == &<span EG(uninitialized_zval_ptr)) {
            &hellip;&hellip;
        }
        </span><span else</span> <span if</span> (Z_TYPE_PP(array_ptr_ptr) ==<span  IS_OBJECT) {
            &hellip;&hellip;
        }
        </span><span else</span><span  {
            </span><span //</span><span  针对遍历array的情况</span>
            <span if</span> (Z_TYPE_PP(array_ptr_ptr) ==<span  IS_ARRAY) {
                SEPARATE_ZVAL_IF_NOT_REF(array_ptr_ptr);
                </span><span if</span> (opline->extended_value &<span  ZEND_FE_FETCH_BYREF) {
                    </span><span //</span><span  将保存array的zval设置为is_ref</span>
<span                     Z_SET_ISREF_PP(array_ptr_ptr);
                }
            }
            array_ptr </span>= *<span array_ptr_ptr;
            Z_ADDREF_P(array_ptr);
        }
    } </span><span else</span><span  {
        &hellip;&hellip;
    }
    &hellip;&hellip;
}</span>

问题2中已经分析了一部分FE_RESET的实现。这里需要特别注意,本例foreach获取值采用了引用,因此在执行的时候FE_RESET中会进入与上题不同的另一个分支。

最终,FE_RESET会将array的is_ref设置为true,此时内存中只有一份array的数据。

接下来分析SEND_REF:

<span static</span> <span int</span><span  ZEND_FASTCALL  ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    &hellip;&hellip;
    </span><span //</span><span  从CV中获取$arr指针的指针</span>
    varptr_ptr = _get_zval_ptr_ptr_cv(&opline-><span op1, EX(Ts), BP_VAR_W TSRMLS_CC);
    &hellip;&hellip;
    
    </span><span //</span><span  变量分离,</span><span 由于此时CV中的变量本身就是一个引用,</span><span 此处不会copy一份新的array</span>
<span     SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);
    varptr </span>= *<span varptr_ptr;
    Z_ADDREF_P(varptr);
    
    </span><span //</span><span  压栈</span>
<span     zend_vm_stack_push(varptr TSRMLS_CC);

    ZEND_VM_NEXT_OPCODE();
}</span>

宏SEPARATE_ZVAL_TO_MAKE_IS_REF仅仅分离is_ref=false的变量。由于之前array已经被设置了is_ref=true,因此它不会被拷贝一份副本。换句话说,此时内存中依然只有一份array数据。

上图解释了前2次循环为何会输出1=>b 2=>C。在第3次循环FE_FETCH的时候,将指针继续向前移动。

ZEND_API <span int</span> zend_hash_move_forward_ex(HashTable *ht, HashPosition *<span pos)
{
    HashPosition </span>*current = pos ? pos : &ht-><span pInternalPointer;

    IS_CONSISTENT(ht);

    </span><span if</span> (*<span current) {
        </span>*current = (*current)-><span pListNext;
        </span><span return</span><span  SUCCESS;
    } </span><span else</span>
        <span return</span><span  FAILURE;
}</span>

由于此时内部指针已经指向了数组的最后一个元素,因此再向前移动会指向NULL。将内部指针指向NULL之后,我们再对数组调用key和current,则分别会返回NULL和false,表示调用失败,此时是echo不出字符的。

 问题4:

<span $arr</span> = <span array</span>(1, 2, 3<span );
</span><span $tmp</span> = <span $arr</span><span ;
</span><span foreach</span>(<span $tmp </span><span as</span> <span $k</span> => &<span $v</span><span ){
    </span><span $v</span> *= 2<span ;
}
</span><span var_dump</span>(<span $arr</span>, <span $tmp</span>); <span //</span><span  打印什么?</span>

该题与foreach关系不大,不过既然涉及到了foreach,就一起拿来讨论吧:)

代码里首先创建了数组$arr,随后将该数组赋给了$tmp,在接下来的foreach循环中,对$v进行修改会作用于数组$tmp上,但是却并不作用到$arr。

为什么呢?

这是由于在php中,赋值运算是将一个变量的值拷贝到另一个变量中,因此修改其中一个,并不会影响到另一个。

题外话:这并不适用于object类型,从PHP5起,对象的便总是默认通过引用进行赋值,举例来说:

<span class</span><span  A{
    </span><span public</span> <span $foo</span> = 1<span ;
}
</span><span $a1</span> = <span $a2</span> = <span new</span><span  A;
</span><span $a1</span>->foo=100<span ;
</span><span echo</span> <span $a2</span>->foo; <span //</span><span  输出100,$a1与$a2其实为同一个对象的引用</span>

回到题目中的代码,现在我们可以确定$tmp=$arr其实是值拷贝,整个$arr数组会被再复制一份给$tmp。理论上讲,赋值语句执行完毕之后,内存中会有2份一样的数组。

也许有同学会疑问,如果数组很大,岂不是这种操作会很慢?

幸好php有更聪明的处理办法。实际上,当$tmp=$arr执行之后,内存中依然只有一份array。查看php源码中的zend_assign_to_variable实现(摘自php5.3.26):

<span static</span> inline zval* zend_assign_to_variable(zval **variable_ptr_ptr, zval *value, <span int</span><span  is_tmp_var TSRMLS_DC)
{
    zval </span>*variable_ptr = *<span variable_ptr_ptr;
    zval garbage;
    &hellip;&hellip;
  </span><span //</span><span  左值为object类型</span>
    <span if</span> (Z_TYPE_P(variable_ptr) == IS_OBJECT && Z_OBJ_HANDLER_P(variable_ptr, <span set</span><span )) {
        &hellip;&hellip;
    }
    </span><span //</span><span  左值为引用的情况</span>
    <span if</span><span  (PZVAL_IS_REF(variable_ptr)) {
        &hellip;&hellip;
    } </span><span else</span><span  {
        </span><span //</span><span  左值refcount__gc=1的情况</span>
        <span if</span> (Z_DELREF_P(variable_ptr)==<span 0</span><span ) {
            &hellip;&hellip;
        } </span><span else</span><span  {
            GC_ZVAL_CHECK_POSSIBLE_ROOT(</span>*<span variable_ptr_ptr);
            </span><span //</span><span  非临时变量</span>
            <span if</span> (!<span is_tmp_var) {
                </span><span if</span> (PZVAL_IS_REF(value) && Z_REFCOUNT_P(value) > <span 0</span><span ) {
                    ALLOC_ZVAL(variable_ptr);
                    </span>*variable_ptr_ptr =<span  variable_ptr;
                    </span>*variable_ptr = *<span value;
                    Z_SET_REFCOUNT_P(variable_ptr, </span><span 1</span><span );
                    zval_copy_ctor(variable_ptr);
                } </span><span else</span><span  {
                    </span><span //</span><span  $tmp=$arr会运行到这里,<br />                    // value为指向$arr里实际array数据的指针,variable_ptr_ptr为$tmp里指向数据指针的指针
                    </span><span //</span><span  仅仅是复制指针,并没有真正拷贝实际的数组</span>
                    *variable_ptr_ptr =<span  value;
                    </span><span //</span><span  value的refcount__gc值+1,本例中refcount__gc为1,Z_ADDREF_P之后为2</span>
<span                     Z_ADDREF_P(value);
                }
            } </span><span else</span><span  {
                &hellip;&hellip;
            }
        }
        Z_UNSET_ISREF_PP(variable_ptr_ptr);
    }

    </span><span return</span> *<span variable_ptr_ptr;
}</span>

可见$tmp = $arr的本质就是将array的指针进行复制,然后将array的自动加1.用图表达出此时的内存,依然只有一份array数组:

既然只有一份array,那foreach循环中修改$tmp的时候,为何$arr没有跟着改变?

继续看PHP源码中的ZEND_FE_RESET_SPEC_CV_HANDLER函数,这是一个OPCODE HANDLER,它对应的OPCODE为FE_RESET。该函数负责在foreach开始之前,将数组的内部指针指向其第一个元素。

<span static</span><span  int ZEND_FASTCALL  ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    zend_op </span>*opline =<span  EX(opline);

    zval </span>*array_ptr, **<span array_ptr_ptr;
    HashTable </span>*<span fe_ht;
    zend_object_iterator </span>*iter = <span NULL</span><span ;
    zend_class_entry </span>*ce = <span NULL</span><span ;
    zend_bool is_empty </span>= 0<span ;

    </span><span //</span><span  对变量进行FE_RESET</span>
    <span if</span> (opline->extended_value &<span  ZEND_FE_RESET_VARIABLE) {
        array_ptr_ptr </span>= _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts),<span  BP_VAR_R TSRMLS_CC);
        </span><span if</span> (array_ptr_ptr == <span NULL</span> || array_ptr_ptr == &<span EG(uninitialized_zval_ptr)) {
            &hellip;&hellip;
        }
        </span><span //</span><span  foreach一个object</span>
        <span else</span> <span if</span> (Z_TYPE_PP(array_ptr_ptr) == <span IS_OBJECT</span><span ) {
            &hellip;&hellip;
        }
        </span><span else</span><span  {
            </span><span //</span><span  本例会进入该分支</span>
            <span if</span> (Z_TYPE_PP(array_ptr_ptr) == <span IS_ARRAY</span><span ) {
                </span><span //</span><span  注意此处的SEPARATE_ZVAL_IF_NOT_REF<br />                // 它会重新复制一个数组出来
                // 真正分离$tmp和$arr,变成了内存中的2个数组</span>
<span                 SEPARATE_ZVAL_IF_NOT_REF(array_ptr_ptr);
                </span><span if</span> (opline->extended_value &<span  ZEND_FE_FETCH_BYREF) {
                    Z_SET_ISREF_PP(array_ptr_ptr);
                }
            }
            array_ptr </span>= *<span array_ptr_ptr;
            Z_ADDREF_P(array_ptr);
        }
    } </span><span else</span><span  {
        &hellip;&hellip;
    }
    
    </span><span //</span><span  重置数组内部指针</span>
<span     &hellip;&hellip;
}</span>

从代码中可以看出,真正执行变量分离并不是在赋值语句执行的时候,而是推迟到了使用变量的时候,这也是Copy On Write机制在PHP中的实现。

FE_RESET之后,内存的变化如下:

上图解释了为何foreach并不会对原来的$arr产生影响。至于ref_count以及is_ref的变化情况,感兴趣的同学可以详细阅读ZEND_FE_RESET_SPEC_CV_HANDLER和ZEND_SWITCH_FREE_SPEC_VAR_HANDLER的具体实现(均位于php-src/zend/zend_vm_execute.h中),本文不做详细剖析:)

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Vorheriger Artikel:php curl 相关知识Nächster Artikel:define常量