Пользователь
0,0
рейтинг
16 декабря 2012 в 01:07

Разработка → Как устроены переменные в PHP tutorial

PHP*
Вроде простой вопрос, даже не понятно что на него ответить, правда?
Мы все знаем как создать переменную, как получить значение переменной, как взять ссылку на переменную в конце концов.
Но как они работают изнутри?
Что происходит в интерпретаторе, когда вы изменяете значение переменной? Или когда удаляете ее?
Как реализованы типы переменных?

В этой статье я постараюсь раскрыть именно эти темы.

Abstract

Переменные в PHP выражены в виде неких контейнеров, которые хранят в себе тип переменной, значение, кол-во ссылающихся переменных на этот контейнер, и флаг — является ли эта переменная ссылочной.



Отступление про структуры и указатели

Если вы никогда не писали на Си, то возможно не знаете про такие вещи, как структуры и указатели, которые очень широко тут используются и без которых пожалуй было бы очень сложно представить себе хоть сколько нибудь сложную программу на Си.
Структуры очень похожи на классы, только они не могут иметь методов, только данные, указатели на данные и указатели на функции. Объявляя структуру в Си, вы определяете тип данных, и теперь при определении переменной, вы можете написать имя этой структуры на месте типа той переменной, примерно так:
my_super_struct super_struct_instance;

Указатели — это как переменные-ссылки, только их значение — это адрес в памяти. На самом деле, это ссылки как указатели, только они ведут себя как разыменованные указатели. Лучше показать на коде:
// создали указатель foo, который будет указывать на переменную типа int
int *foo;
// создали переменную типа int
int bar = 3;

// взяли ссылку на переменную bar и присвоили ее указателю.
// теперь foo хранит адрес ячейки памяти, в которой хранится bar
foo = &bar;

// с помощью астериска мы разыменовываем указатель (берем значение по его адресу)
// и инкрементируем значение
(*foo)++;

// а так мы инкрементируем сам указатель, то есть после этой
// операции указатель будет смотреть на другое значение
foo++;



Контейнеры

Контейнером служит структура под названием zval, она выглядит так:
struct zval {
    zvalue_value value;
    zend_uchar type; // можно предположить, что это обычный char
    zend_uchar is_ref;
    zend_ushort refcount;
};

Как мы видим, здесь есть значение, тип, флаг и кол-во ссылающихся переменных.
Здесь есть такие типы, как:
  • LONG
  • BOOL
  • DOUBLE
  • STRING
  • ARRAY
  • OBJECT
  • RESOURCE
  • NULL

zvalue_value — это union. Union — это такой тип, в котором можно объявить несколько членов разных типов, но использоваться в итоге будет только один, вот как он дефайнится:

typedef union _zvalue_value {
        long lval; // integer
        double dval; // float
        struct {
                char *val;
                int len;
        } str; // string
        HashTable *ht; // array
        zend_object obj; // object
} zvalue_value;

В итоге, когда вы будете создавать переменную этого типа, она займет в памяти ровно столько, сколько занимает самый тяжелый элемент юниона.


Зачем столько лишнего?

Теперь разберем — зачем тут, например, какой-то refcount?
А очень просто: когда вы присваиваете переменной значение другой переменной, то они обе ссылаются на один zval, а refcount инкрементируется.

(оригинал с собачкой тут)

Теперь, если вы захотите изменить значение одной из этих переменных, то PHP, увидя refcount больше 1, скопирует этот zval, сделает изменения там, и ваша переменная будет указывать уже на новый zval.
Если это немного формализовать, то это будет выглядеть примерно так:
PHP Под капотом
$foo = "asd";
$bar = $foo;
bar,foo: {
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 0
    refcount: 2
}
$bar .= "q";
foo: {
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 0
    refcount: 1
}
bar: {
    type: string,
    value:
        str:
            val: "asdq"
            len: 4
    is_ref: 0
    refcount: 1
}

Эта техника называется copy on write и она позволяет неплохо снизить потребление памяти.
Также, refcount нужен сборщику мусора, который удаляет из памяти все zval-ы, у которых refcount = 0.

А что делать с ссылками и зачем вообще этот is_ref?

А что происходит со ссылками? Все очень просто: если вы создаете ссылку от переменной, то флаг is_ref становится равным 1, и больше вышеописанная оптимизация для этого zval-а применяться не будет. Поясню кодом:
PHP Под капотом
$foo = "asd";
$bar = $foo;
bar,foo: {
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 0
    refcount: 2
}
$zxc = &$foo;
zxc,foo: {
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 1
    refcount: 2
}
bar: { // переменная bar была выделена в отдельный zval
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 0
    refcount: 1
}
$qwe = $foo;
zxc,foo: {
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 1
    refcount: 2
}
bar: {
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 0
    refcount: 1
}
qwe: { // эта переменная тоже была выделена в отдельный zval
    type: string,
    value:
        str:
            val: "asd"
            len: 3
    is_ref: 0
    refcount: 1
}


Конечно, если вы возьмете еще одну ссылку от foo, то refcount zval-а, на который ссылается foo, увеличится на один.

Пожалуй на этом (пока?) все, в следующей части поговорим о массивах.

PS не знаю кто как воспримет эти картинки, мне показалось это будет забавно :) к сожалению сканера у меня нет
Никита Нефедов @nikita2206
карма
137,2
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (25)

  • +30
    Картинки — жесть…

    PS: а за статью спасибо, познавательно
  • +6
    не знаю кто как воспримет эти картинки, мне показалось это будет забавно :) к сожалению сканера у меня нет
    Картинки неплохие, но стоило бы привести их в порядок.

    Например, если немного подкрутить контраст, яркость, и сбросить насыщенность, то:
    Было, 230КБ
    Стало, 80Кб

    * если бы рисунок был нарисован на белой бумаге, было бы лучше.
    • +15
      было

      стало

      Но ведь это две разные картинки?
      • +2
        Одна и та же, но с подкрученной яркостью. Неужели неочевидно? )
        • +6
          Вы нас вводите в заблуждение. Присмотритесь внимательнее.
      • +2
        Да, ошибся пока копипастил ссылки… Глупо получилось :)
    • 0
      Не-не, в листочках в клеточку ессть какой-то непередаваемый гиковский шарм.
  • +29
    За картинки давай зачетку.
  • +2
    Теперь, если вы захотите изменить значение одной из этих переменных, то PHP, увидя refcount больше 1, скопирует этот zval, сделает изменения там, и ваша переменная будет указывать уже на новый zval.
    Если это немного формализовать, то это будет выглядеть примерно так:

    Действительно новость.

    P.S Картинки умилили) Спасибо!
  • +4
    Интересно увидеть человека,
    у которого гуманитарные способности (Рисование) развито в сильной мере, как и техническое (программирование и логика).

    Да Винчи первый, кто приходит на ум.

    Это так, отступление. А пост очень в тему и полезный, спасибо большое.
    • +2
      Еще же автор xkcd :)
    • +2
      Простите, но что за картинки вы смотрите, раз считаете, что у автора тех, что выше развиты, способности к рисованию?
    • +1
      По-моему программирование вполне таки творческая деятельность!
  • 0
    Где-то я подобное уже видел… Раз, два
    • 0
      Да, на английском и немецком материал есть, особенно советую блог Никиты Попова (nikic на гитхабе), но на русском что-то как-то — крохи там, крохи тут.
      • 0
        • 0
          Вау, никогда не находил этот блог. Ну про массивы я все равно напишу, но ссылку там обязательно эту оставлю.
    • 0
      Оу, если бы я за каждую свою подобную фразу, при прочтении постов хабраюзера номер раз, получал по 5 рублей… а тут материал действительно достойный внимания. Ну и за креативчик плюс.
  • +1
    Пишите еще. Спасибо.
  • 0
    Как применить эти знания — я к сожалению не додумался, но автору однозначно спасибо! Иногда очень полезно разобрать подробно те вещи/инструменты, которыми всегда пользовался и не задумывался о том, как всё функционирует.
    • +2
      Некоторые любят «оптимизировать» свои программы, используя везде где возможно ссылки, то есть в сигнатурах функций принимают переменные по ссылкам, вот эта оптимизация совершенно не нужна, т.к. интерпретатор сам обо всем позаботится.
    • 0
      Ну теперь можно посчитать насколько избыточна модель хранения переменных в PHP… я для себя весь этот ужос раскопал когда начал свои extensions писать для PHP… Впрочем Zend engine не самое страшное зло в этом мире =)
      • 0
        Вообще говоря, зенд энжин относительно красивое зло, IMO, даже я бы сказал и не зло вовсе, за исключением разве что «парсинга на лету» (не-AST).
  • 0
    Спасибо за статью. Ждем продолжения.
  • 0
    Пожалуй на этом (пока?) все

    Спасибо, давайте еще!

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.