Метка: Xdebug

  • Почему после модификации массива refcount становится 0? Разбор работы Copy-on-Write в PHP 8.3

    Разбор работы refcount и Copy-on-Write в PHP

    Рассмотрим неочевидное поведение подсчёта ссылок при работе с массивами в PHP 8.3.
    В предыдущей статье был простой пример

    $original = [1];
    $copy = $original;
    $copy[0] = 999;
    xdebug_debug_zval('copy');
    // Вывод: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=999)
    // Реальный refcount = 0 (новая копия)
    

    Возник вопрос, почему после операции копирования refcount=0. Разберем подробнее в этой статье.

    1. Пошаговый разбор примера

    Шаг 1: Создание массива

    $original = [1];
    xdebug_debug_zval('original');
    // Вывод: (refcount=2, is_ref=0)=array (0 => (refcount=0, is_ref=0)=1)
    

    Объяснение:

    • Реальный refcount=1 (переменная $original)
    • Xdebug добавляет +1 при выводе (поэтому показывает 2)
    • Элемент массива 1 имеет refcount=0 — это оптимизация для чисел

    Шаг 2: Присваивание переменной

    $copy = $original;
    xdebug_debug_zval('original');
    // Вывод: (refcount=3, is_ref=0)=array (...)
    

    Объяснение:

    • Реальный refcount=2 ($original + $copy)
    • Xdebug добавляет +1 (поэтому показывает 3)
    • Обе переменные ссылаются на один zval

    Шаг 3: Модификация массива (COW)

    $copy[0] = 999;
    xdebug_debug_zval('copy');
    // Вывод: (refcount=1, is_ref=0)=array (0 => (refcount=0, is_ref=0)=999)
    

    Ключевой момент:

    • Срабатывает Copy-on-Write — создаётся новая копия массива
    • Xdebug показывает refcount=1, значит реальный refcount=0
    • Это означает, что только $copy ссылается на этот zval
    • Если сделать unset($copy) — массив сразу удалится

    2. Почему реальный refcount=0?

    В PHP 8.3 действуют следующие правила:

    • Новый zval после COW начинается с refcount=0
    • Переменная $copy — единственный владелец этого zval
    • Xdebug добавляет +1 при выводе (поэтому показывает 1)
    • Элементы массива всегда refcount=0 — это оптимизация

    3. Визуализация в памяти

    ОперацияСостояние памяти
    $original = [1][ZVAL1: refcount=1, value=[1]]
    $copy = $original[ZVAL1: refcount=2, value=[1]]
    $copy[0] = 999 [ZVAL1: refcount=1, value=[1]]
    [ZVAL2: refcount=0, value=[999]]

    4. Практические выводы

    • После COW новый массив имеет refcount=0 (Xdebug показывает 1)
    • Это нормальное поведение оптимизированного PHP 8.3
    • Элементы массива всегда refcount=0 — не стоит беспокоиться
    • Для точных измерений используйте memory_get_usage()

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