为啥 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 【问题描述】:

这是一个关于foreachphp 中实现方式背后原因的好奇问题。

考虑:

$arr = array(1,2,3);
foreach ($arr as $x) echo current($arr) . PHP_EOL;

将输出:

2
2
2

我了解foreach 将数组指针倒回到开头;但是,为什么它只增加一次呢?魔盒里面发生了什么??这只是一个(丑陋的)人工制品吗?


感谢@NickC——如果有其他人对zvalrefcount 感兴趣,您可以阅读基础知识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 31 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。 内部数组指针移动到下一个元素,即现在指向2current 被调用,$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,指针等等)

php foreach为啥比for效率高

Java中foreach为啥不能给数组赋值

PHP正则表达式;数组:for()遍历 foreach ()遍历each()list()组合遍历;指针遍历

PHP 数组遍历 foreach 语法结构

PHP中如何使用foreach结构遍历数组?