Ссылки в PHP
16 июля 2018
Официальная документация: https://www.php.net/manual/ru/language.references.php
Подсчёт ссылок для сборки мусора: https://www.php.net/manual/ru/features.gc.refcounting-basics.php
Если поле is_ref__gc содержит 1 (true), то переменная является ссылкой. Но тут есть тонкий момент – если на переменную ссылается какая-нибудь другая переменная, то исходная переменная тоже становится ссылкой. Это можно проверить через расширение Xdebug с помощью функции xdebug_debug_zval
Поле refcount__gc указывает сколько переменных используют участок памяти. Если на переменную никто не ссылается, то поле будет иметь значение 1, так как ячейку памяти использует только сама переменная. В случае, когда на переменную будут ссылаться, например, 2 другие переменные, то поле refcount__gc будет содержать значение 3. Когда refcount__gc становится равным нулю, область памяти переменной (структура zval) освобождается сборщиком мусора.
Следующий код выведет refcount=2, хотя по задумке refcount должен иметь значение 1. Это происходит из-за оптимизации управления памятью в PHP, когда система передаёт аргумент в функцию через ссылку, а так как ссылки у переменной нету, то она создаётся и таким образом в области видимости функции переменная имеет refcount=2. Подробнее о таком поведении можно почитать в документации https://www.php.net/manual/ru/function.debug-zval-dump.php
Сброс связи ссылки и значения переменной происходит с помощью функции unset, при этом происходит именно удаление связи, а не значения. Участок памяти освобождается, когда на него не адресуется ни одна переменная (refcount=0)
Иногда пробуют сбрасывать связь присваиванием значения null, но так делать нельзя, т.к операция присвоения присваивает значение.
Подсчёт ссылок для сборки мусора: https://www.php.net/manual/ru/features.gc.refcounting-basics.php
Введение
Ссылки – это механизм для доступа к значению переменной, с помощью которого несколько переменных-ссылок могут закрепляться за одним участком памяти. Связь ссылки и участка памяти организовано ядром PHP через специальные таблицы имён (Symbol table), которые создаются отдельное для каждой области видимости: глобально, на уровне скрипта, а так же для каждой функции. В таблице имён хранится название переменной и указатель на ячейку памяти. В ячейке памяти хранится структура типа zval, которая состоит из полей: value, type, refcount__gc, is_ref__gc. Описание структуры:typedef struct _zval_struct {
zvalue_value value; /* значение переменной */
zend_uint refcount__gc; /* количество переменных, ссылающихся на участок памяти */
zend_uchar type; /* тип значения */
zend_uchar is_ref__gc; /* флаг "это ссылка" */
} zval;
Если поле is_ref__gc содержит 1 (true), то переменная является ссылкой. Но тут есть тонкий момент – если на переменную ссылается какая-нибудь другая переменная, то исходная переменная тоже становится ссылкой. Это можно проверить через расширение Xdebug с помощью функции xdebug_debug_zval
$a = 'DDD';
xdebug_debug_zval('a'); /* Выведет a: (refcount=2, is_ref=0)string 'DDD' (length=3) */
$b = &$a; /* Объявление ссылки $b */
$a = 'AAA';
xdebug_debug_zval('a'); /* Выведет a: (refcount=2, is_ref=1)string 'AAA' (length=3) */
unset($b);
xdebug_debug_zval('a'); /* Выведет a: (refcount=1, is_ref=1)string 'AAA' (length=3) */
Поле refcount__gc указывает сколько переменных используют участок памяти. Если на переменную никто не ссылается, то поле будет иметь значение 1, так как ячейку памяти использует только сама переменная. В случае, когда на переменную будут ссылаться, например, 2 другие переменные, то поле refcount__gc будет содержать значение 3. Когда refcount__gc становится равным нулю, область памяти переменной (структура zval) освобождается сборщиком мусора.
Следующий код выведет refcount=2, хотя по задумке refcount должен иметь значение 1. Это происходит из-за оптимизации управления памятью в PHP, когда система передаёт аргумент в функцию через ссылку, а так как ссылки у переменной нету, то она создаётся и таким образом в области видимости функции переменная имеет refcount=2. Подробнее о таком поведении можно почитать в документации https://www.php.net/manual/ru/function.debug-zval-dump.php
$a = 'DDD';
xdebug_debug_zval('a'); /* Выведет a: (refcount=2, is_ref=0)string 'DDD' (length=3) */
Объявление ссылки
Ссылка объявляется с помощью оператора &$a = null;
$b = &$a; /* Теперь значение переменной-ссылки $b будет адресоваться по тому же участку памяти, что и значение $a */
$a = 'AAA';
echo $b; /* Выведет 'ААА' */
Сброс связи ссылки и значения переменной происходит с помощью функции unset, при этом происходит именно удаление связи, а не значения. Участок памяти освобождается, когда на него не адресуется ни одна переменная (refcount=0)
$a = 'DDD';
$b = &$a; /* Переменная $b стала ссылаться на значение $a, но и $a стала ссылкой на значение */
unset($a); /* Сбросили связь */
$a = 'EEE'; /* Так как $a перестала куда-либо ссылаться, то при присвоении нового значения на это выделяется новый участок памяти */
echo $a; /* Выведет 'EEE' */
echo $b; /* Выведет 'DDD' */
Иногда пробуют сбрасывать связь присваиванием значения null, но так делать нельзя, т.к операция присвоения присваивает значение.
$a = 'DDD';
$b = &$a;
$b = null; /* null только сбросит значение, а связь ссылки и ячейки памяти сохранится */
var_dump($a); /* Выведет 'NULL' */
var_dump($b); /* Выведет 'NULL' */
$a = 'DDD';
var_dump($a); /* Выведет 'string(3) "DDD"' */
var_dump($b); /* Выведет 'string(3) "DDD"' */
Передача аргумента функции по ссылке
Для передачи в функцию параметра по ссылке используется оператор &, который указывается перед аргументом при объявлении функции./* Функция прибавляет к метке времени 1 день */
function add_day_to_timestamp(&$timestamp)
{
$timestamp = $timestamp + 86400;
return true;
}
$current_ts = date('U'); /* Получаем текущую метку времени */
echo date('d.m.Y H:i:s', $current_ts); /* Выведет текущую дату (16.07.2018 09:02:55) */
echo '<br>';
add_day_to_timestamp($current_ts); /* Увеличиваем метку времени на одни сутки */
echo date('d.m.Y H:i:s', $current_ts); /* Выведет текущую дату, увеличенную на 1 сутки, т.е '17.07.2018 09:02:55' */
Возврат значения функции по ссылке
Для возврата значения функции по ссылке используется оператор &, который указывается в двух местах:- Перед названием функции в момент её объявления;
- После оператора =, когда присваивается значение функции.
$GLOBALS['current_day'] = date('d.m.Y'); /* Текущая дата '16.07.2018' */
function &get_global_var($var)
{
$var = trim((string)$var);
if($var=='') exit('Ошибка. У функции get_global_var в параметре $var указано пустое значение');
else
{
if(isset($GLOBALS[$var])==false) $GLOBALS[$var] = '';
return $GLOBALS[$var]; /* Возврат значения (ссылки на значение) */
}
}
$a = &get_global_var('current_day'); /* Теперь $a указывает на ячейку значения, как и $GLOBALS['current_day'] */
echo $a; /* Выведет '16.07.2018' */
echo '<br>';
$GLOBALS['current_day'] = '10.07.2018';
echo $a; /* Выведет '10.07.2018' */
echo '<br>';
$a = '12.07.2018';
echo $GLOBALS['current_day']; /* Выведет '12.07.2018' */
Особенности языка
- При объявлении глобальных переменных с помощью инструкции GLOBAL создаются не отдельные самостоятельные переменные, а ссылки, указывающие на элемент глобального массива $GLOBALS;
- В классах ключевое слово $this является ссылкой на созданный экземпляр класса (ссылкой на объект).