深入解析php中的foreach问题(4)


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


问题2中已经分析了一部分FE_RESET的实现。这里需要特别注意,本例foreach获取值采用了引用,因此在执行的时候FE_RESET中会进入与上题不同的另一个分支。
最终,FE_RESET会将array的is_ref设置为true,此时内存中只有一份array的数据。
接下来分析SEND_REF:

复制代码 代码如下:


static int ZEND_FASTCALL  ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
    ……
    // 从CV中获取$arr指针的指针
    varptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC);
    ……

    // 变量分离,由于此时CV中的变量本身就是一个引用,此处不会copy一份新的array
    SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);
    varptr = *varptr_ptr;
    Z_ADDREF_P(varptr);

    // 压栈
    zend_vm_stack_push(varptr TSRMLS_CC);
    ZEND_VM_NEXT_OPCODE();
}


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

深入解析php中的foreach问题

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

复制代码 代码如下:


ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos)
{
    HashPosition *current = pos ? pos : &ht->pInternalPointer;
    IS_CONSISTENT(ht);
    if (*current) {
        *current = (*current)->pListNext;
        return SUCCESS;
    } else
        return FAILURE;
}


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

复制代码 代码如下:


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


该题与foreach关系不大,不过既然涉及到了foreach,就一起拿来讨论吧:)
代码里首先创建了数组$arr,随后将该数组赋给了$tmp,在接下来的foreach循环中,对$v进行修改会作用于数组$tmp上,但是却并不作用到$arr。
为什么呢?
这是由于在php中,赋值运算是将一个变量的值拷贝到另一个变量中,因此修改其中一个,并不会影响到另一个。
题外话:这并不适用于object类型,从PHP5起,对象的便总是默认通过引用进行赋值,举例来说:

复制代码 代码如下:

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:http://www.heiqu.com/3b117f60371124faf01845086ad90b7f.html