前言

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

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

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

问题1

$arr = array(,,);

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

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

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

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

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

根据上述理论,现在我们重新来分析下第一个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()来将其销毁。
$arr = array(1,2,3);

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

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

问题2

$arr = array('a','b','c');

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

这个问题更加诡异。按照手册的说法,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表,因而可以提高效率。

static int ZEND_FASTCALL  ZEND_ASSIGN_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zend_free_op free_op2;
zval *value = _get_zval_ptr_tmp(&opline->op2, EX(Ts), &free_op2 TSRMLS_CC); // CV数组中创建出$arr**指针
zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC); if (IS_CV == IS_VAR && !variable_ptr_ptr) {
……
}
else {
// 将array赋值给$arr
value = zend_assign_to_variable(variable_ptr_ptr, value, TSRMLS_CC);
if (!RETURN_VALUE_UNUSED(&opline->result)) {
AI_SET_PTR(EX_T(opline->result.u.var).var, value);
PZVAL_LOCK(value);
}
} ZEND_VM_NEXT_OPCODE();
}

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

接下来执行数组的循环操作,我们来看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, , 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);
varptr = *varptr_ptr;
Z_ADDREF_P(varptr); // 压栈
zend_vm_stack_push(varptr TSRMLS_CC); ZEND_VM_NEXT_OPCODE();
}

上述代码中的SEPARATE_ZVAL_TO_MAKE_IS_REF是一个宏:

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

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

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

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

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

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

问题3

$arr = array('a','b','c');

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

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

首先foreach会调用FE_RESET:

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数据。

上图解释了前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起,对象的便总是默认通过引用进行赋值,举例来说:

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

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

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

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

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

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

aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAXkAAACCCAIAAAAsQy8EAAANw0lEQVR4nO2d328TRwLH+2ctVOSogKCQiJSnPKB1W9SmvcjXQkFpQMkRGgmp0xRSdDElihRoQx1iCRQFoihWUBJFYBUQAsupVESgRoVz+HFRMXdQHuYe1o5317O2Z707s+P9fvR9AHvXXg8zH+aX1+9QAADwn3dkXwAAIBTANQAAEcA1AAARwDUAABHANQAAEVR3zeOn63PXV2IXFruOT2g6UTpdxyeGxq9eXko/fPxcQOE2Bo1UARDPU3ubquKac9Oppn0npH8ePxK7sPjmr7feNcnGpIErAOJ5KrcpR9c8W89v/D/W1Hl62xc/tfRM7T46135sXtX0J1t6prbvP7+1a9T4XB3do789zPnTSJWnASsA4nl42pSja4x6tvmD75oPTsr/SF5n1+Gpd/ed0nTSGh1++eq1bw1WYRq7AiCep2qbYrvm3HTKqGdtfTPSP4Nf6U82dZ7WdDIwMuNzs1WPUFQAxPNUbFMM1zx8/NwYou88lJB/9X6mrW9mU2RQ08n1O6v+t19lCE8FQDxPhTbFcM3F+duaTrZ+NiL9ugVk+/7zmk6Gxq8KacVqEKoKgHgepzbFcM03Y3OaTnYc+Fn6RQtIS8+UppOPj40LacVqEKoKgHgepzbFcE2k96ymk12Hp6RftIDsPjqn6aRp3wkhrVgNQlUBakpsNUtzRPJlpOJrdDkhuyhqiFObYrjGWLtq709Kv2gx0SKDmk6EtGI1CFsFqBqSoTSTtjyYyFFKswup6qfHVrOloq1FWOllh+OjC3m6thqVXRq1hNmmnF0j+3LFlYtO4BozYasA1ZJepuYORSq+ViioGlyTiq/l4zHTiVVkUfH42GqWbjwb6DDbFFwD19gJWwWokkTO3L+ILuQpzZFj6eUa+zX2l+KRhf34VHyN/01lBK7hKJcwo0IFKHUuTCIwmmK68FQmzXqEfS7J2Hocpb6Mw8hFhGuKXis9UnadAQ1cw1EuYSbwFaBsoGGViEkBzEdY59osYOrLMCZrjs27c025Oyoltpotewu+V5AXuIajXMKMWhXA1O8onxCpMkViOtfiDpNfzC4zh981LHewYpobLn9f64AusIFrOMolzKhQAcyLNdTiGkv7ZJqCea7JO7HVbGky2GmKhNc1tUwM20My1D7mgmuUDlxjI/AVwNLO7f2aKq5xOre0ymMdp3jTryEZ6soRZe8O19RecAHcjATX2Ai6a6xLv3yucTx34+BVW0em/vkat6KZL+9VYb6Go+DgmuCjgmuKLdDYKcfnGua58+2Flkxtw5ba16GYIogu5J3WnhjHWzcol5+LdajqIRnTGzD+lwhcuYSZoLvmWGHbLqWU0nx8Icc3X8M+10h6mZY1Zsb+GivF12e5xjo3VP142+vbJIX9NdVilbF50TEQgWtsKOAav8IcGdn2DVc6l6fbzns89g27/PcLUOAaG+F1jcNeO4cpG0uiC3muDjvv8e1qfR9KkmucZvJNzxbg3QDqY7mEh4W7Tz46sXxtpXSP2LC6xnlZGt/z5oy0MVRxCFr+r1XPBlB/yyVU/Ppo/cuRX3rGbt5/8icNr2sQzyJ1Hcr0zXonX9SzAdTzcgkhV355tJcs/mv6180fnoRrkHoifc17vr24IGXqCtazAdSzcnnvHz/t+mcSMdLSO7ul8wfp9RVRN4FwjdUa9WwA9bdcQsi///PfgfidzlPXtnT+gH4NUk+C55p6NoD6XC6hIv+/t2PJe3vJ4uTSA4r5GqTuSHJNbDVrnmdJ5OjGGKqeDaA+l0t4+PXRujFN8+LlG+ORxnbNxmY5VdZ0VIzUvXwlyr+3WnycdwOon+USHu4/+dNYftqgkV0T/O1w3PcnDmLkjqHSy0EtuJC7ppxGdo2U70lzvCnv/YkDGulr3nCNGsA1QXlT3vsTByYBmRsOXOAaG6pVAPaecvOXGI2pGetY3nSLT+sjtnNNI3f7QN52+77sQqr0asXDKk0gVA1c02CBa2yoVgEYe8otX/c1t1hrF8NyZ5lEzhCE9evX6WXqeD9Q+61CrQsdpeux9WssMzJFWHORqtytpjxwDUe5hBnVKkD5vEZ62emnTizNnvlN67IHS6qq5hr2U27HULXenziIgWs4yiXMqFYB2DfEslP6vYRis2euSZU/WHpEpGsUnhhuh2u4yiXMqFYBqtzo05IguKaGMVQdtw0NROAajnIJM6pVAHa/hr1Vz+6assPKH3T+rSif+jWqi6YdruEqlzCjWgVg7PO0NVeSKeqjfG7YPIWcSZedW/aVPdsCVq2uqXU5qcL9iRUKXMNRLmFGtQrA3lNuXmwu9VPsXQzmrdos59pmZzeeyi6kTPfWq+ia0rtUlUil+xMrFLiGo1zCTNgqAOJ5anXNngNnNJ20HpmWfsUi0p/cFBmEa8yEqwIgnsehTTFc0zc8remk+eCk/Iv2P61HpjWdRHrPCmnFahCqCoB4Hqc2xXDNxOwNTSfvRcekX7SANH85oelkYGRGSCtWg1BVAMTzOLUphmturWQ1nWyKDLb1XpF+3f6mP/nuvlOaTi7O3xbSitUgRBUA8TzObYrhGlrsRW/5JCb/0v3Mts9/1HTS0T365q+3/jdhlQhJBUA8T4U2xXbNy1evW6PDmk62do3uPjon/QN4n/6kUSiaTtL3/vC/8SpG41cAxPNUa1Ns11BKb61kd3z6vaaTzR+ebD442TCrEm29V3YeShjdPE0nE7M3/GyzCtOoFQDxPDW2KUfXUEqfref3f5swzm+QRAY3/tzRPYoeTWUasAIgnqfmNlXJNQaXl9J9w9Md3aPyP5UX2XPgzFdDlyZmb2COpkYarAJ4k8iguY2FPDW2qequAQDYGL96f/zqfdlXoRhwDQB8vHj55v2v59//en7jV25ALcA1APAxEL+zlyzuJYsD8Tuyr0Ul4BoAOLj74MVesjiWvGf8UujdBy9kX5EywDUAcBCNpa6t5Iz5mmsruWgsJfuKlAGuAaBWLl37vWfsJjXNDfeM3bx07XfZ16UGcA0AtTK59ODR0zw1uebR0/zk0gPJl6UIcA0A3GDN2wVwDQDcwDUugGsA4AaucQFcAwA3cI0L4BoAuIFrXADXAMANXOMCuAYAbuAaF8A1AHAD17gArgGAG7jGBXANANzANS6AawDgBq5xAVwDADdwjQuqu+bx0/W56yuxC4tdxyek39m0znQdnxgav3p5Kf3w8XMBhQsaFbjGBVVcc2461bTvhHRH+JHYhUXc3hy4A65xgaNrnq3nNzoyTZ2nt33xU0vPlNo/S9afbOmZ2r7//Nauwk8CdHSP/vYwJ7K4QWMA17jA0TWGaDZ/8F3zwUn5mvA6uw5PGT+d1RodfvnqtcgSBw0AXOMCtmvOTacM0bT1zUj3gl/pTzZ1ntZ0MjAyI7jQgerANS5guObh4+fGHM3OQwn5RvAzbX0zmyKDmk6u31kVX/RAXeAaFzBcc3H+tqaTrZ+NSHeBgGzff17TydD4VfFFD9QFrnEBwzXfjM1pOtlx4GfpIhCQlp4pTScfHxsXX/RAXeAaFzBcE+k9q+lk1+Ep6SIQkN1H5zSdNO07Ib7ogbrANS5guMZYD27vT0oXgZgYPwIvvuiBusA1LnB2jWwFiHONTuAawAVc4wK4Bq4B3MA1LoBr4BrADVzjArgGrgHcwDUugGvgGsANXOMCuAauAdzANS6Aa+AawA1c4wK4Bq4B3MA1LvDbNan4Gl1OyBcKXAM8BK5xAVwD1wBu4BoX+OgakjG9aCYt3SlwDfAKuMYFfrmGZChdW40W/pqKr+XjseKziRylOSJbMXANcA1c4wKfXJNepjS7kGI/C9cAxYFrXOCTa1LxNfa4yTKwokZnJxVfo9mFVOmpTLrwCpRSap7uKRwZXcgXnit1neAaIA64xgV+jaGKOmD1X+z9moJWCv2gROGHDQzFRBfypoOLAipYLL1MPZgJgmsAL3CNC/xch4qtZouvaRlPMV1TUoZ1/BVbzZa6NvbuktVE7l2z+aOhuw9eIEiNOXEpA9fwImIvnzE4Kg2FqrimMFAyq8fJNe2JXHEgVpdr/vb30Z6xmwhSe+4/+VN0Y1UcMfuGrY4InmswhgLAb9R2jVdjKLgGAL/xxzWx1ax5hSiRo/YxlLkzwuma0mxOxZV1uAaAIOHnXr4StmHOxnp2ac2bq19jXR2v6zrhGgDE4OsYKr3s8Z49x207cA0AAcfvNW+4BgBAqWr3r4FrAFAVtVzjS+AaAAQA18A1AIgAroFrABABXAPXACACuAauAUAEcA1cA4AI4Bq4BgARMFyz58AZTSetR6alW0BE+pObIoNwDQB+w3BN3/C0ppPmg5PyReB/Wo9MazqJ9J4VX/QAhAqGayZmb2g6eS86Jl0EAtL85YSmk4GRGfFFD0CoYLjm1kpW08mmyGBb7xXpLvA3/cl3953SdHJx/rb4ogcgVDBcQ4vDqC2fxOTrwM9s+/xHTScd3aNv/noruNwBCBts17x89bo1OqzpZGvX6O6jc9Kl4H36k4ZoNJ2k7/0huNABCCFs11BKb61kd3z6vaaTzR+ebD442TDLUm29V3YeShhDJ00nE7M3RBY3AKHF0TWU0mfr+f3fJow22SCJDG78uaN7FD0aAIRRyTUGl5fSfcPTHd2j8k3hRfYcOPPV0KWJ2RuYowFAJNVdAwAA9QPXAABEANcAAEQA1wAARADXAABE8H9i9ELEKVRtyQAAAABJRU5ErkJggg==" alt="" />

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

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

static int ZEND_FASTCALL  ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline); zval *array_ptr, **array_ptr_ptr;
HashTable *fe_ht;
zend_object_iterator *iter = NULL;
zend_class_entry *ce = NULL;
zend_bool is_empty = 0; // 对变量进行FE_RESET
if (opline->extended_value & ZEND_FE_RESET_VARIABLE) {
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)) {
……
}
// foreach一个object
else if (Z_TYPE_PP(array_ptr_ptr) == IS_OBJECT) {
……
}
else {
// 本例会进入该分支
if (Z_TYPE_PP(array_ptr_ptr) == IS_ARRAY) {
// 注意此处的SEPARATE_ZVAL_IF_NOT_REF
// 它会重新复制一个数组出来
// 真正分离$tmp和$arr,变成了内存中的2个数组
SEPARATE_ZVAL_IF_NOT_REF(array_ptr_ptr);
if (opline->extended_value & ZEND_FE_FETCH_BYREF) {
Z_SET_ISREF_PP(array_ptr_ptr);
}
}
array_ptr = *array_ptr_ptr;
Z_ADDREF_P(array_ptr);
}
} else {
……
} // 重置数组内部指针
……
}

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

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

aaarticlea/png;base64,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" alt="" />

上图解释了为何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中),本文不做详细剖析:)

php中的foreach问题(1)的更多相关文章

  1. “mybatis 中使用foreach 传

    为了帮助网友解决“mybatis 中使用foreach 传”相关的问题,中国学网通过互联网对“mybatis 中使用foreach 传”相关的解决方案进行了整理,用户详细问题包括:mybatismap ...

  2. C#中的foreach语句与枚举器接口(IEnumerator)及其泛型 相关问题

    这个问题从<C#高级编程>数组一节中的foreach语句(6.7.2)发现的. 因为示例代码与之前的章节连贯,所以我修改了一下,把自定义类型改为了int int[] bs = { 2, 3 ...

  3. 基于mysql对mybatis中的foreach进行深入研究

    鉴于上一篇博文一次修改mysql字段类型引发的技术探究提到的,要对foreach里面的collection相关的内容做一些介绍,今天就围绕foreach,做一些数据插入和查询相关的研究. 首先介绍一下 ...

  4. 在弹框中获取foreach中遍历的id值,并传递给地址栏(方法2)

    1.php有时候我们需要再弹框中获取foreach中遍历的数据(例如id),在弹框中点击按钮并传递给地址栏跳转.那么应该怎么做呢.第二种方法. 2. 可以在弹框中给出一个input hidden 点击 ...

  5. 在弹框中获取foreach中遍历的id值,并传递给地址栏。

    1.php有时候我们需要再弹框中获取foreach中遍历的数据(例如id),在弹框中点击按钮并传递给地址栏跳转.那么应该怎么做呢. 2. 点击取现按钮,如果没有设置密码->弹框 3. 点击去设置 ...

  6. 深入解析php中的foreach问题

    本篇文章是对php中的foreach问题进行了详细的分析介绍,需要的朋友参考下   前言:php4中引入了foreach结构,这是一种遍历数组的简单方式.相比传统的for循环,foreach能够更加便 ...

  7. JS中的forEach、$.each、map方法推荐

    下面小编就为大家带来一篇JS中的forEach.$.each.map方法推荐.小编觉得挺不错的,现在分享给大家,也给大家做个参考.一起跟随小编过来看看吧 orEach是ECMA5中Array新方法中最 ...

  8. 如何在JMeter中使用ForEach控制器

    Jmeter中的ForEach Controller遍历变量数组. 在这个JMeter教程中,我们将使用ForEach控制器循环访问JSON数组. 有时我们需要解析响应并提取某些信息.例如,在测试AP ...

  9. 集合中的 for-Each循环

     数组的加强型的for-Each循环很简单,我们再来看一下集合中的for-Each 循环又是怎么样的.我们都知道集合中的遍历都是通过迭代(iterator)完成的.也许有人说,也可以按照下面的方式来遍 ...

随机推荐

  1. cesium运行环境搭建

    cesiumjs是什么 一个世界级3D地球仪和地图的开源JavaScript库. 1.安装node.js 环境 1)下载node.js 官网:https://nodejs.org/en/ 下载完成后双 ...

  2. javax.el.PropertyNotFoundException: Property [name] not readable on type

    该错误为el表达式读取javaBean属性时报错. 如: {$user.name} 原因: javaBean Class访问权限不够 解决办法: 将javaBean Class设置为public即可 ...

  3. Octotree Chrome安装与使用整理

    Octotree Chrome作用: 主要使你在github查看项目时可以清晰明了的看到项目的结构以及具体代码,使下载代码更具有目的性,减少不必要代码的下载,而且看起来更清楚. 效果图:(安装插件前) ...

  4. Vue-Render函数理解示例

    对应文档节点: https://vuefe.cn/v2/guide/render-function.html#Slots <body> <div id="app" ...

  5. 流畅的python和cookbook学习笔记(一)

    1.数据结构 1.1 内置序列类型 四种序列类型: 1.容器序列:list.tuple和collections.deque 2.扁平序列:str.bytes.bytearray.memoryview和 ...

  6. docker容器启动时执行脚本 run /bin/bash执行多条指令

    搜了很多资料发现并未解决,以下方法失败!求大神评论给出完美方案 1.首先需要编写需要启动的脚本,并将脚本放在 /etc/init.d/目录下 如:cs.sh 2.修改权限 3.chkconfig -- ...

  7. thinkphp的删除操作

    1.循环遍历要删除的用户的或者呀删除的文章的id值: <volist name="list" id="vo"> <tr id="si ...

  8. 【转】JSON.parse()与JSON.stringify()的区别

    JSON.parse()[从一个字符串中解析出json对象] 例子: //定义一个字符串 var data='{"name":"goatling"}' //解析 ...

  9. replace的坑

    问题:html中代码段包含了$,在使用replace替换时,$直接被替换了解决:先把文本中的$全部替换成自己定义的标签,最后在还原回去原因:在介绍replace的文档中,$&代表插入匹配的子串 ...

  10. css 样式表集合

    说到前端不得不说一下css样式 css样式是用来装饰我们的html让整个页面显得更丰富多彩,所以我们要熟悉各种css样式,本人搜集了一下 供大家参考一下 字体属性:(font) 大小 {font-si ...