为啥 PHP 的 foreach 将其数组的指针(仅)推进一次?
Posted
技术标签:
【中文标题】为啥 PHP 的 foreach 将其数组的指针(仅)推进一次?【英文标题】:Why does PHP's foreach advance the pointer of its array (only) once?为什么 PHP 的 foreach 将其数组的指针(仅)推进一次? 【发布时间】:2012-01-05 23:46:42 【问题描述】:这是一个关于foreach
在 php 中实现方式背后原因的好奇问题。
考虑:
$arr = array(1,2,3);
foreach ($arr as $x) echo current($arr) . PHP_EOL;
将输出:
2
2
2
我了解foreach
将数组指针倒回到开头;但是,为什么它只增加一次呢?魔盒里面发生了什么??这只是一个(丑陋的)人工制品吗?
感谢@NickC——如果有其他人对zval
和refcount
感兴趣,您可以阅读基础知识here
【问题讨论】:
回波电流($arr)是怎么回事?您没有在 foreach 循环中使用 $arr 。 foreach($arr as $x) echo current($arr).PHP_EOL; 哈——是的,为了美观,我试图优化我的循环,但后来取出了一个关键部分!foreach
对数组的副本进行操作。我不确定为什么它实际上会改变数组指针。
我预计它会生成1 1 1
,因为我认为它会在副本上运行。但后来我重读了de.php.net/manual/en/control-structures.foreach.php 和http://nikic.github.com/2011/11/11/PHP-Internals-When-does-foreach-copy.html,但输出应该是1 2 3
或1 1 1
而不是2 2 2
。非常好的问题!
这不是重复的。链接的问题是"I did array stuff inside foreach and everything breaks?!? make it go away"
,这是"I want a technical explanation of the inner workings of PHP regarding foreach loop behavior"
【参考方案1】:
就在第一次迭代之前,$array
被“软复制”以用于foreach
。这意味着没有进行实际的复制,只是将$array
的zval的refcount
增加到2
。
第一次迭代:
-
值被提取到
$x
。
内部数组指针移动到下一个元素,即现在指向2
。
current
被调用,$array
通过引用传递。由于引用,PHP 不能再与循环共享zval
,需要将其分开(“硬拷贝”)。
在接下来的迭代中,$array
zval 不再与foreach
zval 相关。因此它的数组指针不再被修改,current
总是返回相同的元素。
对了,我在foreach copying behavior上写了一个小总结。它可能在上下文中很有趣,但它与问题没有直接关系,因为它主要讨论的是硬拷贝。
【讨论】:
第一次迭代:reset + next。这是记录在案的。但是试图通过 ref 迭代并在循环内使用 reset() 来愚弄这一点,但无法做到。我想这是一种保护。 @MarkTomlin 不会,因为 foreach 只会将其视为表达式而不是变量。我不确定为什么 hakre 将它添加到答案中。 我认为refcount
实际上增加到 3 因为 $arr 在 foreach
-head (foreach ($arr ...
.
@PhilippeGerber 是的,循环中的 debug_zval_dump(当前之前)给出 refcount 4,所以在循环开始后它必须是 3。你知道额外的参考来自哪里吗?我在 FE_RESET 中只找到了一个 ADDREF
我认为您可以将对意外/未定义行为的搜索范围缩小到 PHP 5.2.2 和 5.2.4 之间的更改 - 可能与所有关于 Fixed bug #41372 (Internal pointer of source array resets during array copying).
的引用有关 - 在此之前对于 OP 示例,所有版本都返回了 1 1 1
。【参考方案2】:
如果我们稍微修改一下代码,看看有多有趣:
$arr = array(1,2,3);
foreach ($arr as &$x) echo current($arr) . PHP_EOL;
我们得到了这个输出:
2
3
一些有趣的参考资料:
http://nikic.github.com/2011/11/11/PHP-Internals-When-does-foreach-copy.html
http://blog.golemon.com/2007/01/youre-being-lied-to.html
现在,试试这个:
$arr = array(1,2,3);
foreach ($arr as $x) $arr2 = $arr; echo current($arr2) . PHP_EOL;
输出:
2
3
1
这确实很好奇。
那么这个呢:
$arr = array(1,2,3);
foreach ($arr as $x) $arr2 = $arr; echo current($arr) . ' / ' . current($arr2) . PHP_EOL;
echo PHP_EOL;
foreach ($arr as $x) $arr2 = $arr; echo current($arr2) . ' / ' . current($arr2) . PHP_EOL;
输出:
2 / 2
2 / 2
2 / 2
2 / 2
3 / 3
1 / 1
似乎发生的事情就像 NickC 的答案中所写,加上这样一个事实,即当将数组作为参数传递给 current
函数时,因为它是通过引用传递的 em>,里面的东西确实修改了作为参数传递给它的数组......
【讨论】:
【参考方案3】:这是您使用 php 5.3 进行代码操作码分析的结果。
看这个例子:http://php.net/manual/en/internals2.opcodes.fe-reset.php
操作数:15 编译后的变量:!0 = $arr, !1 = $x
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > INIT_ARRAY ~0 1
1 ADD_ARRAY_ELEMENT ~0 2
2 ADD_ARRAY_ELEMENT ~0 3
3 ASSIGN !0, ~0
3 4 > FE_RESET $2 !0, ->13
5 > > FE_FETCH $3 $2, ->13
6 > ZEND_OP_DATA
7 ASSIGN !1, $3
8 SEND_REF !0
9 DO_FCALL 1 'current'
10 CONCAT ~6 $5, '%0A'
11 ECHO ~6
12 > JMP ->5
13 > SWITCH_FREE $2
14 > RETURN 1
有关详细信息,请参阅 NikiC 的答案,但您在第 8 行看到 !0 在循环中永远不会改变。(5-12)
【讨论】:
以上是关于为啥 PHP 的 foreach 将其数组的指针(仅)推进一次?的主要内容,如果未能解决你的问题,请参考以下文章
PHP遍历数组常用方式(for,foreach,while,指针等等)