Почему 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 , вы можете прочитать об refcount здесь

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

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

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

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

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

Посмотрите, насколько интересно, если мы немного изменим код:

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

Это результаты анализа кода кода кода с помощью 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)