为什么PHP的foreach一次推进其数组的指针(仅)?

时间:2011-11-24 23:04:38

标签: php

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

考虑:

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

将输出:

2
2
2

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


感谢@NickC - 对于其他对zvalrefcount感到好奇的人,您可以阅读基础知识here

3 个答案:

答案 0 :(得分:15)

在第一次迭代之前,$array被“软复制”,以便在foreach中使用。这意味着没有完成实际的副本,但只有refcount的zval的$array增加到2

在第一次迭代中:

  1. 该值将被提取到$x
  2. 内部数组指针移动到下一个元素,即现在指向2
  3. 通过引用传递current来调用
  4. $array。由于引用PHP无法再与循环共享zval,因此需要将其分开(“硬复制”)。
  5. 在接下来的迭代中,$array zval因此不再与foreach zval相关。因此,它的数组指针不再被修改,current总是返回相同的元素。

    顺便说一下,我在foreach copying behavior上写了一个小摘要。它可能对上下文感兴趣,但它与问题没有直接关系,因为它主要涉及硬拷贝。

答案 1 :(得分:3)

如果我们稍微改变一下代码,请看看有多有趣:

$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回答中所写, plus 将数组作为参数传递给current函数的事实,因为它通过引用传递 ,里面的东西会修改作为参数传递给它的数组......

答案 2 :(得分:1)

这是使用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)