php foreach current,解析php中的foreach问题

解析php中的foreach问题

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

复制代码 代码如下:

static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

{

……

if (……) {

……

} else {

// 通过CV数组获取指向array的指针

array_ptr = _get_zval_ptr_cv(&opline->op1, EX(Ts), BP_VAR_R TSRMLS_CC);

……

}

……

// 将指向array的指针保存到zend_execute_data->Ts中(Ts用于存放代码执行期的temp_variable)

AI_SET_PTR(EX_T(opline->result.u.var).var, array_ptr);

PZVAL_LOCK(array_ptr);

if (iter) {

……

} else if ((fe_ht = HASH_OF(array_ptr)) != NULL) {

// 重置数组内部指针

zend_hash_internal_pointer_reset(fe_ht);

if (ce) {

……

}

is_empty = zend_hash_has_more_elements(fe_ht) != SUCCESS;

// 设置EX_T(opline->result.u.var).fe.fe_pos用于保存数组内部指针

zend_hash_get_pointer(fe_ht, &EX_T(opline->result.u.var).fe.fe_pos);

} else {

……

}

……

}

这里主要将2个重要的指针存入了zend_execute_data->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:

复制代码 代码如下:

static int ZEND_FASTCALL ZEND_FE_FETCH_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

{

zend_op *opline = EX(opline);

// 注意指针是从EX_T(opline->op1.u.var).var.ptr获取的

zval *array = EX_T(opline->op1.u.var).var.ptr;

……

switch (zend_iterator_unwrap(array, &iter TSRMLS_CC)) {

default:

case ZEND_ITER_INVALID:

……

case ZEND_ITER_PLAIN_OBJECT: {

……

}

case ZEND_ITER_PLAIN_ARRAY:

fe_ht = HASH_OF(array);

// 特别注意:

// FE_RESET指令中将数组内部元素的指针保存在EX_T(opline->op1.u.var).fe.fe_pos

// 此处获取该指针

zend_hash_set_pointer(fe_ht, &EX_T(opline->op1.u.var).fe.fe_pos);

// 获取元素的值

if (zend_hash_get_current_data(fe_ht, (void **) &value)==FAILURE) {

ZEND_VM_JMP(EX(op_array)->opcodes+opline->op2.u.opline_num);

}

if (use_key) {

key_type = zend_hash_get_current_key_ex(fe_ht, &str_key, &str_key_len, &int_key, 1, NULL);

}

// 数组内部指针移动到下一个元素

zend_hash_move_forward(fe_ht);

// 移动之后的指针保存到EX_T(opline->op1.u.var).fe.fe_pos

zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.u.var).fe.fe_pos);

break;

case ZEND_ITER_OBJECT:

……

}

……

}

根据FE_FETCH的实现,我们大致上明白了foreach($arr as $k => $v)所做的事情。它会根据zend_execute_data->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:

复制代码 代码如下:

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);

……

// 变量分离,此处重新copy了一份array专门用于key函数

SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值