PHP:
$a = array("key" => 23);
var_dump($a);
$c = &$a["key"];
var_dump($a);
unset($c);
var_dump($a);
Выход:
array(1) {
["key"]=>
int(23)
}
array(1) {
["key"]=>
&int(23)
}
array(1) {
["key"]=>
int(23)
}
Во втором дампе значение «ключ» отображается в виде ссылки. Это почему?
Если я делаю то же самое с обычной переменной вместо ключа массива, этого не происходит.
Мое единственное объяснение состоит в том, что ключи массива обычно хранятся в виде ссылок, и если в таблице символов есть только одна запись, она отображается в виде скаляра в дампе.
Внутренне PHP-массивы — это хеш-карты (или словари, или HashTables, или как вы хотите их называть). Даже численно индексированный массив реализован в виде хеш-таблицы, которая является zval
, как и любой другой.
Однако то, что вы видите, — это ожидаемое поведение, которое объясняется оба здесь а также Вот.
По сути, ваш массив выглядит так:
typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
} zval;
//zval_value:
typedef union _zvalue_value {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
} zvalue_value;
В случае массива zval.type
будет установлен, чтобы указать, что zval
значение является массивом, и поэтому zval_value.ht
член будет использоваться.
Что происходит, когда вы пишете $c = &$a['key']
это то, что zval
который назначен $a['key']
будет обновлено: zval.refcount__gc
будет увеличен, и is_ref__gc
будет установлен в 1. Просто потому, что значение не копируется, но значение используется более чем одной переменной: это значение является ссылка. Однажды ты unset($c);
, refcount
уменьшается, и ссылка теряется, и так is_ref
установлен в 0
,
Теперь о большом: почему вы не видите то же самое, когда используете обычные скалярные переменные? Ну, это потому, что массив является HashTable, в комплекте со своим собственным, внутренним подсчетом ссылок (zval_ptr_dtor
). Если сам массив пуст, его тоже следует уничтожить. Создавая ссылку на значение массива, и вы сбрасываете массив, zval
должен быть GC’ed. Но это будет означать, что у вас есть ссылка на уничтоженный zval
плавать вокруг.
Следовательно zval
в массиве тоже изменяется ссылка: ссылку можно безопасно удалить. Так что, если вы должны были сделать это:
$foo = array(123);
$bar = &$foo[0];
unset($foo[0]);
echo $bar, PHP_EOL;
Ваш код все еще будет работать, как и ожидалось: $foo[0]
больше не существует, но $bar
теперь единственная существующая ссылка на 123.
Это просто очень, очень короткое и неполное объяснение, но посмотрите на внутреннюю часть PHP и то, как работает управление памятью, как обрабатываются ссылки внутри, и как сборщик мусора использует is_ref
а также refcount
члены для управления памятью.
Обратите особое внимание на внутренние механизмы, такие как копирование при записи, и (просматривая первую ссылку, которую я предоставил здесь), найдите фрагмент, который выглядит следующим образом:
$ref = &$array;
foreach ($ref as $val) {}
Потому что он имеет дело с некоторыми странностями с точки зрения ссылок и массивов.
Других решений пока нет …