Нашли баг в PHP: перезапись последнего значения массива предпоследним при использовании foreach со ссылкой на значения массива

17 комментариев
Нашли баг в PHP: перезапись последнего значения массива предпоследним при использовании foreach со ссылкой на значения массива
Чтобы воспроизвести баг я написал вот такой небольшой пример:
" microtime(1), $i); //fill array with some examples 
} 

foreach ($a as &$b) {
//do nothing, with link on value '&'
}

foreach ($a as $b) {
//do nothing, without '&'
}

print_r($a); //LOOK AT LAST 2
По умолчанию в переборе foreach для значения создается копия элемента из массива, но если нам надо внести правки в исходные значения - можно создать ссылку на элемент массива. Суть проблемы - переменная AS из форича доступна в глобальном скоупе после конца цикла и содержит последний элемент перебора, в нашем случае ссылку на последний элемент массива. Используя это же имя переменной в следующем переборе мы каждой итерацией перезаписываем последний элемент исходного массива по ссылке вплоть до предпоследнего. Все проблемы из-за отсутствия четкой типизации в языке. Для быстрого фикса подобной проблемы нужно между первым и вторым циклом выполнить unset($b);. UPD: Благодаря комментарию юзера xpurpur стало известно, что проблема стара и даже описана в мануале.

Горячие события

Конкурс EY Entrepreneur Of The Year 2020
31 мая — 31 мая

Конкурс EY Entrepreneur Of The Year 2020

GoWayFest 4.0
11 июля — 11 июля

GoWayFest 4.0

Минск

Читайте также

Состоялся релиз PHP 7.4
Состоялся релиз PHP 7.4

Состоялся релиз PHP 7.4

Уязвимость PHP7 подвергает сайты риску удалённого взлома
Уязвимость PHP7 подвергает сайты риску удалённого взлома

Уязвимость PHP7 подвергает сайты риску удалённого взлома

3 комментария
7 языков программирования, которые стоит изучать в 2019 году
7 языков программирования, которые стоит изучать в 2019 году

7 языков программирования, которые стоит изучать в 2019 году

5 комментариев
Python назвали самым популярным языком программирования
Python назвали самым популярным языком программирования

Python назвали самым популярным языком программирования

Обсуждение

0

1) Наличие эффекта подтверждаю.

2) Я не большой знаток PHP, и, может быть, поэтому не понимаю смысла конструкции

foreach ($a as &$b) {
//do nothing, with link on value '&'
}

т. е. для чего тут используется ссылка? Что хочет сказать этим выражением автор???

Другими словами, я не вижу логики в таком использовании конструкции foreach и единственное, в чём могу упрекнуть интерпретатор PHP, так это в том, что он не вываливается с [зачеркнуть]runtime error[конец_зачеркивания] ошибкой предпроверки.

P.S. Проверял в PHP Version 5.1.2 для Windows, apache-модуль.

0

В учебниках по PHP часто рекомендуют пользоваться ссылкой в цикле, если в массиве необходимо произвести изменения в значениях. Следующий цикл, перебирающий этот же массив получит неправильный последний элемент.

Необходимое условие: использование одинакового имени переменной в foreach для значения.

1

Понял, согласен. Подтверждаю наличие проблемы.

1

Это не баг, а особенность. Читаем документацию (http://php.net/manual/en/control-structures.foreach.php) внимательнее и видим:

foreach (array(1, 2, 3, 4) as &$value) {
$value = $value * 2;
}
Warning
Reference of a $value and the last array element remain even after the foreach loop. It is recommended to destroy it by unset().

А также обсуждение особенности вот тут:
http://bugs.php.net/bug.php?id=29992

0

Благодарю, обновил пост

0

Вообще-то это баг, только не в имплементации а в концепции языка.

1

И, тем не менее, считаю пост полезным. Потому что напоминание лишним не будет даже для тех, кто знал о "граблях", не говоря уже о тех, у кого они впереди ;-)

Anonymous
Anonymous Lead Software Engineer в EPAM
1

Уважаемый автор, вы бы хоть обьяснили более популярно суть проблемы для людей, не знакомых с синтаксисом PHP: что такое в этом языке "ссылка на значение", например. Здесь, все-таки, блог рассчитанный на массового читателя, который, чтобы понять техническую суть, не будет 10 раз лезть в гугл, и сожержание статьи должно хоть как-то это учитывать.

PS: никсолько не отрицаю полезность материала для PHP спецов

0

По умолчанию в переборе foreach для значения создается копия элемента из массива, но если нам надо внести правки в исходные значения - можно создать ссылку на элемент массива.
Суть проблемы - переменная AS из форича доступна в глобальном скоупе после конца цикла и содержит последний элемент перебора, в нашем случае ссылку на последний элемент массива. Используя это же имя переменной в следующем переборе мы каждой итерацией перезаписываем последний элемент исходного массива по ссылке вплоть до предпоследнего.

Максим  Гулевич
Максим Гулевич Дизайнер в EPAM
3

Нифига не понял. Можно описать суть проблемы для идиотов вроде меня?

0

По умолчанию в переборе foreach для значения создается копия элемента из массива, но если нам надо внести правки в исходные значения - можно создать ссылку на элемент массива.
Суть проблемы - переменная AS из форича доступна в глобальном скоупе после конца цикла и содержит последний элемент перебора, в нашем случае ссылку на последний элемент массива. Используя это же имя переменной в следующем переборе мы каждой итерацией перезаписываем последний элемент исходного массива по ссылке вплоть до предпоследнего.

1

В двух словах, поскольку я только что начал работать с PHP, и насколько я увидел проблему.

Имеем массив. Поскольку для демонстрации этот массив нужно создать, автор примера написал первые три строки кода:
for ($i=" 0;" $i< 10; ++$i) {
$a[$i] = $i;
}
Если у автора массив заполняется текущим временем в миллисекундах, то в нашем случае получим массив из 10 элементов a[0]...a[9], заполненный числами 0, 1, 2, ... 9. Это -- тестовый полигон. Дальше начинается демонстрация проблемы.

Конструкция foreach в PHP выполняет цикл, на входе в каждую итерацию которого второй переменной присваивается значение очередного элемента массива, представленного первой переменной (вот наговорил!) Давайте смотреть на примере:

foreach ($a as &$b) {
//do nothing, with link on value '&'
}

Переменная $a -- массив. Переменная $b -- параметр цикла. Цикл выполняется столько раз, сколько элементов в массиве $a, причём на каждой итерации $b присваивается значение очередного элемента массива $a: на 1-й итерации $b = $a[0], на второй $b = $a[1] и т. д.

Амперсанд перед переменной $b сообщает о том, что в нашем конкретном цикле $b будет получать не копию значения элемента массива, а ссылаться на конкретный элемент (сравните с передачей параметров по ссылке и по значению в функции Паскаля или Си++). Т.е. при такой записи если мы напишем $b = 100, то изменим значение в самом массиве $a.

Последний цикл foreach в приведенном выше примере -- это точно такой же цикл, как и второй, только $b является не ссылкой, а переменной. То есть он полностью аналогичен второму циклу, однако, если мы в этом цикле запишем $b = 100, то на элементах массива $a это никак не отразится.

Функция print_r () просто распечатывает содержимое массива.

Автор поста обратил внимание вот на какое явление. Если выполнить приведенный выше код, то после его выполнения почему-то $a[8] = $a[9] = 8! Хотя должно было бы быть $a[8] = 8, $a[9] = 9. Это и есть проблема.

P.S. Для тех, кто не привык к синтаксису PHP: значок "доллара" не означает ничего кроме того, что следующий за ним идентификатор -- переменная. Т. е. все переменные в PHP обязаны начинаться с $. $ -- это обязательная первая буква идентификатора переменной ;-) Ну, а & перед переменной означает, что она -- ссылка. Как в Си++.

1

хотелось бы добавить, что при использование конструкции foreach($array as $value) переменная $value остается доступной в программе

foreach($array as $value);
echo $value; //выводит значение последнего элемента массива

отсюда следует, что
foreach ($a as &$b) {
//do nothing, with link on value '&'
}
аналогично
$b = &$a[9];

т.е. после выполнения первого цикла $b ссылается на $a[9].

ну а далее второй цикл (foreach ($a as $b)) делает следующее:
$b = $a[0]; //$a[9] => 0
$b = $a[1]; //$a[9] => 1
$b = $a[2]; //$a[9] => 2
....
$b = $a[8]; //$a[9] => 8
$b = $a[9]; //$a[9] => 8

0

Вот тут все встало на свои места

1

Спасибо, очень наглядно продемонстрирована причина такого казалось бы необъяснимого результата. Оказывается, нет ничего удивительного. Более того, всё логично! ;-)

0

В суть проблемы не вникал, но всё же не совсем понятно почему в РБ тема Python & Django намного меньше развита, нежели , скажем, в Украине ... :)

-1

Совсем оффтоп :)