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