Подтвердить что ты не робот

Почему PHP foreach один раз увеличивает указатель своего массива (только)?

Это вопрос любопытства о причинах, по которым способ foreach реализован в PHP.

Рассмотрим:

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

который выведет:

2
2
2

Я понимаю, что foreach перематывает указатели массива на начало; однако почему он увеличивает его только один раз? Что происходит внутри волшебной коробки? Это просто (уродливый) артефакт?


Спасибо @NickC - для всех, кого интересуют zval и refcount, вы можете прочитать основные сведения здесь

4b9b3361

Ответ 1

Прямо перед первой итерацией $array "мягко скопирована" для использования в foreach. Это означает, что фактическая копия не выполняется, но только refcount zval от $array увеличивается до 2.

На первой итерации:

  • Значение выбирается в $x.
  • Внутренний указатель массива перемещается в следующий элемент, т.е. теперь указывает на 2.
  • current вызывается с $array, переданным по ссылке. Из-за ссылки PHP не может делиться zval с циклом, и его нужно разделить ( "скопировано" ).

В следующих итерациях $array zval, следовательно, больше не связан с foreach zval. Таким образом, его указатель массива больше не изменяется, а current всегда возвращает один и тот же элемент.

Кстати, я написал небольшое резюме в foreach copying. Это может быть интересно в контексте, но оно напрямую не связано с проблемой, поскольку в основном речь идет о жестком копировании.

Ответ 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, поскольку он передается по ссылке, что-то внутри там изменить массив, переданный в качестве аргумента для него...

Ответ 3

Это результат анализа кода кода кода с помощью php 5.3.

Смотрите этот пример: http://php.net/manual/en/internals2.opcodes.fe-reset.php

количество операций: 15 скомпилированные vars:! 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)