Страницы

пятница, 27 августа 2010 г.

Ссылки и foreach

Это перевод статьи которую в написал Johannes Schlüter в своем блоге. Оригинал можно найти перейдя по ссылке References and foreach. Я использовал изображения из его поста, добавив подписью, перевод надписей. На самом деле это вообще мой первый перевод, который я публикую, так что надеюсь что получилось не слишком плохо.

Ссылки в PHP ужасны, как я уже говорил и вы безусловны должны избегать их использование. Так вот, есть один пример, который приводит на первый взгляд к неожиданному поведению, которое я еще не видел, до того как я наткнулся на него в первый раз, но затем было несколько бег-репортов об этом инцеденте, а недавно знакомый спросил меня о нем, итак вот он:
Что выведет этот код?
$a = array('a', 'b', 'c', 'd');
foreach ($a as &$v) { }
foreach ($a as $v) { }
print_r($a);

Мы делаем 2 раза итерацию по массиву, ничего не делая с ним. Так что результат не должен измениться. Правильно? - Не Правильно! На самом деле результат будет таким:
Array
(
    [0] => a
    [1] => b
    [2] => c
    [3] => c
)
        
Для того чтобы понять, почему так произошло, сделаем шаг назад и посмотрим как реализованы переменные и ссылки в PHP:

Переменные в PHP в основном состоят из двух вещей. Метка (“label”) и контейнер (“container”). Метка это запись в хэш таблице (есть несколько оптимизацонных моментов в движке, поэтому не всегда в хэш-таблице, ну да ладно) которые могут представлять собой таблицу символов функции, массива или таблицу свойств объекта. Итак, у нас есть имя и указатель на контейнер. Внутреннее название контейнера “zval”, он хранит значения и некую мета информацию, этот контейнер также может быть новой хэш-таблицей с набором меток, указывающих на другие контейнеры, если мы сейчас сделаем ссылку на него, то это приведет ко второй метке указывающей на этот контейнер с другой меткой. Обе метки с этих пор имеют те же полномочия что и контейнер.

Теперь давайте посмотри на ситуацию более детально.
Графически это будет выглядеть так:
Глобальная таблица символов (ГТС)
Итак у нас есть 6 контейнеров (глобальная таблица символов вверху, контейнер содержащий массив называющийся $a слева и по одному контейнеру на каждый элемент - справа) Теперь мы начнем первую итерацию. ГТС получает новую запись $v и это делает ссылку на контейнер первого элемента массива.

Новая глобальная перменная v содержащая контейнер с ‘a’

$a[0] и $v находятся в одном контейнере, следовательно при их изменении одной переменной, на другой будет тот же эффект. Когда итерация продолжится, ссылка оказывается сломанной и $v делает ссылку на другой элемент. Так после окончания итераций $v ссылается на последний элемент.

После итерации $v указывает на ‘d’
Запомните: $v ссылка, а значит любые изменения $v, оказывают тот же эффект и на переменные на которые она ссылается, в нашем случае это я$a[3]. До сих пор не произошло ничего особенного , но сейчас рассмотрим второю итерацию. Она присваивает значение текущего элемента переменной $v для каждого шага. Сейчас $v ссылается на элемент $a[3], так что после присваивания значения к $v, $a[3] тоже изменится.

Для первого шага второй  итерации значение $a[0] присваивается переменной $v
Это продолжается и для следующих шагов:


И теперь мы легко можем догадаться что будет на последнем шаге: $v присваивается значение последнего элемента $a[3] и так как $a[3] является ссылкой на $v, то следовательно ничего не происходит.

И это результат который мы видели выше. Таким образом чтобы сделать короткий вывод из этой истории с картинками: Будьте осторожны с ссылками! Они могут иметь очень странные эффекты.

0 коммент.:

Отправить комментарий