antonio's blog

antonio's blog


Блог о всяком разном, связанном с разработкой ПО. Пишу редко, когда есть время и желание.

Anton Dobkin
Author

Share


Tags


Разработка расширений для PHP. Данные и переменные. Часть 3

Anton DobkinAnton Dobkin

Теперь самое время приступить к созданию переменных, которые потом будут доступны в пользовательском пространстве PHP. Процесс создания переменных не сложнее процесса получения данных хранимых в переменных, который был рассмотрен в предыдущих разделах.

Процесс создания можно разделить на два этапа. Первый этап подготовка данных переменной. Второй этап связывание данных с символическим именем переменной и регистрация нужной таблице символов. Теперь обо всем по порядку.

Первым делом необходимо подготовить данные. Так как данные в Zend Engine представляют из себя структуру zval, то под эту структуру необходимо выделить блок памяти и присвоить указатель на выделенный блок переменной типа zval.

Для выделения памяти под структуру zval необходимо использовать макрос MAKE_STD_ZVAL(). Данный макрос выделяет необходимый блок памяти, автоматически проверяет ошибки связанные нехваткой свободной памяти, а также инициализирует должным образом счетчик ссылок(refcount__gc и is_ref__gc), с помощью макрос INIT_PZVAL(). В качестве аргумента макрос принимает указатель на zval

zval *var = NULL;  
MAKE_STD_ZVAL(var);  

Так же для выделения памяти существует еще один макрос ALLOC_INIT_ZVAL(), который отличается от макроса MAKE_STD_ZVAL() только тем, что он инициализирует данные значением NULL.

После того как память под структуру zval выделена в ней необходимо сохранить данные нужного нам типа. Для инициализации структуры zval Zend Engine предоставляет специальный набор макросов ZVAL_.

ZVAL_NULL(zval *) – инициализирует структуру значением NULL, в качестве аргумента принимает указатель на структуру zval.

Код:

zval *var = NULL;  
MAKE_STD_ZVAL(var);  
ZVAL_NULL(var);  

Эквивалентен:

zval *var = NULL;  
ALLOC_INIT_ZVAL(var);  

ZVAL_BOOL(zval *, b) – инициализирует структуру булевым значением. Первым аргументом принимает указатель на структуру zval, вторым аргументом значение 1 или 0. Если в качестве значения передаётся 1 (или любое другое значение отличное от 0), то переменная будет иметь булево значение TRUE, иначе FALSE. Также существует еще два макроса для инициализации переменной булевым значением: ZVAL_FALSE(zval *) – эквивалент ZVAL_BOOL(zval *, 0) и ZVAL_TRUE(zval *) – эквивалент ZVAL_BOOL(zval *, 1)

ZVAL_LONG(zval *, l) – инициализирует структуру целочисленным значением. Первым аргументом принимает указатель на структуру zval, вторым аргументом целочисленное значение.

ZVAL_DOUBLE(zval *, d) – инициализирует структуру значением c плавающей запятой. Первым аргументом принимает указатель на структуру zval, вторым аргументом значение c плавающей запятой, соответственно.

ZVAL_STRINGL(zval *, str, len, dup) – инициализирует структуру строковым значением. Первым аргументом принимает указатель на структуру zval, вторым аргументом строковые данные, третьим аргументом длину строки, четвертым аргументом флаг копирования строки. Если четвертый аргумент имеет значение 1, то под строку будет выделена новая область памяти и содержимое строки будет в нее скопировано, иначе переменная будет просто связана с уже существующими строковыми данными, в zval будет сохранен указатель на существующие строковые данные.

Также для инициализации структуры строковыми данными существует еще один макрос ZVAL_STRING(zval *, str, dup), данный макрос отличается от предыдущего только тем, что не принимает длину строки в качестве аргумента. Макрос ZVAL_STRINGL() безопасен при работе с двоичными данными.

Для создания пустых строк существует макрос ZVAL_EMPTY_STRING(zval *)

_ZVAL_RESOURCE(zval *, res) – инициализирует структуру значением номера ресурса. Первым аргументом принимает указатель на структуру zval, вторым аргументом целочисленный номер ресурса

Примеры создания и инициализации структуры zval

// Создание переменной со значением NULL
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
ZVAL_NULL(var);

// Создание целочисленной переменной со значением 150
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
ZVAL_LONG(var, 150);

// Создание строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
ZVAL_STRING(var, "String variable", 1);

// Создание пустой строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
ZVAL_EMPTY_STRING(var);  

Использованием макросов ZVAL_ - не единственный способ инициализации структуру zval, но этот способ самый простой и предпочтительный.

Инициализировать структуру можно с использованием уже известных макросов семейства Z_TYPE_* и Z_*VAL_*:

// Создание переменной со значением NULL
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
Z_TYPE_P(var) = IS_NULL;

// Создание целочисленной переменной со значением 150
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
Z_TYPE_P(var) = IS_LONG;  
Z_LVAL_P(var) = 150;

// Создание строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
Z_TYPE_P(var) = IS_STRING;  
Z_STRLEN_P(val) = strlen("String variable");  
Z_STRVAL_P(val) = estrndup("String variable", strlen("String variable") + 1);

// Создание пустой строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
Z_TYPE_P(var) = IS_STRING;  
Z_STRLEN_P(val) = 0;  
Z_STRVAL_P(val) = STR_EMPTY_ALLOC();  

Еще один способ инициализации - это установка вручную значения соответствующих элементов структуры zval:

// Создание переменной со значением NULL
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
new_long->type = IS_NULL;

// Создание целочисленной переменной со значением 150
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
new_long->type = IS_LONG;  
new_long->value.lval = 10;

// Создание строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
var->type = IS_STRING;  
var->value.str.len = strlen("String variable");  
var->value.str.val = estrdup("String variable", strlen("String variable") + 1 );

// Создание пустой строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
var->type = IS_STRING;  
var->value.str.len = 0;  
var->value.str.val = STR_EMPTY_ALLOC();  

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

Теперь когда данные подготовлены их необходимо ассоциировать с символьным названием переменной под которым она будет доступна в пользовательском пространстве PHP.

Таблицы символов

Любая переменная пользовательского пространства PHP храниться во внутреннем контейнере Zend Engine. Когда вы создаете переменную в пользовательском пространстве PHP и задаете ей название, Zend Engine сохраняет это название в этом контейнере, который также известен как таблица символов (symbol table). В Zend Engine существует два типа таблиц символов. Первый тип - это глобальная таблица символов, которая инициализируется до вызова метода PHP_RINIT() и уничтожается перед вызовом метода PHP_RSHUTDOWN(). Переменные зарегистрированные в этой таблице доступны в любом месте сценария.

В момент когда начинает выполняется функция или метод объекта, в пользовательском пространстве PHP, Zend Engine создает новую таблицу символов, локальную, которая существует пока выполняется эта функция или метод. Переменные зарегистрированные в данной таблице символов являются локальными и доступны только в области видимости выполняемой функции или метода. Эта таблица объявляется активной. Таблица является активной пока выполняется функция или метод и все переменные создаваемые в этот момент Zend Engine помещает именно в эту таблицу символов.

Если в текущий момент времени выполнение скрипта не находиться в функции или методе, то активной считается глобальная таблица символов.

В Zend Engine таблицы символов являются элементами глобальной структуры zend_executor_globals. Оба элемента имеют тип HashTable. Глобальная таблица символов храниться в элементе symbol_table, а элемент active_symbol_table является указателем на активную в данный момент таблицу символов:

struct _zend_executor_globals {  
    HashTable symbol_table;          // Глобальная таблица символов
    HashTable *active_symbol_table;  // Активная (локальная) таблица символов
    ...
};

Для доступа к таблицам символов Zend Engine предоставляет макрос EG() (Executor Globals). Необходимо отметить, что данный макрос служит для доступа не только к таблицам символов, но и к другим элементам глобальной структуры zend_execution_globals. В качестве параметра макрос принимает название элемента глобальной структуры.

Чтобы наша переменная стала доступна в пользовательском пространстве, ее нужно зарегистрировать либо в активной в данный момент таблице символов или в глобальной таблице символов, используя макрос ZND_SET_SYMBOL(table, name, zval *). Первым аргументом в макрос передается таблица в которой будет зарегистрирована переменная, вторым аргументом - символическое имя переменной под которым она будет доступна в пользовательском пространстве, третьим - указатель на структуру с данными.

Вызов ZEND_SET_SYMBOL() заносит переменную в таблицу символов перед этим осуществляется проверка существует ли уже переменная с данной меткой в таблице символов и автоматически уничтожает данные, если указатель на них уже есть в таблице символов. Это наиболее предпочтительный способ, если скорость не является критичной и/или необходимо ограничить размер используемой памяти.

// Создание строковой переменной
zval *var = NULL;  
MAKE_STD_ZVAL(var);  
var->type = IS_STRING;  
var->value.str.len = strlen("String variable");  
var->value.str.val = estrdup("String variable", strlen("String variable") + 1 );

// Связывание данных с меткой переменной
ZEND_SET_SYMBOL(EG(active_symbol_table), "my_str_var", var);  

Данный способ является не самым оптимальным с точки зрения производительности и скорости, так как перед регистрацией переменной в таблице символов осуществляется ее поиск в этой таблице.

Если необходима более высокая производительность, а оптимальное использование памяти не является критичным моментом или вы уверены, что переменная с такой меткой не существует в таблице символов, то можно пропустить проверку на существование переменной и форсировать вставку данных в таблицу символов используя zend_hash_update():

zval *var = NULL;  
MAKE_STD_ZVAL(var);  
var->type = IS_STRING;  
var->value.str.len = strlen("String variable");  
var->value.str.val = estrdup("String variable", strlen("String variable") + 1 );

zend_hash_update( EG(active_symbol_table), "my_str_var",  
      strlen("my_str_var") + 1, &var, sizeof(zval *), NULL);

Для полноты картины приведу объявление нескольких макросов из заголовочного файла zend_API.h PHP 5.3.5:

#define ZVAL_LONG(z, l) {           \
        Z_TYPE_P(z) = IS_LONG;      \
        Z_LVAL_P(z) = l;            \
    }

#define ZVAL_STRING(z, s, duplicate) {  \
        const char *__s=(s);            \
        Z_STRLEN_P(z) = strlen(__s);    \
        Z_STRVAL_P(z) =( duplicate ? estrndup(__s, \
                  Z_STRLEN_P(z)):(char*)__s);\
        Z_TYPE_P(z) = IS_STRING;        \
    }

#define ZVAL_EMPTY_STRING(z) {    \
    Z_STRLEN_P(z) = 0;      \
    Z_STRVAL_P(z) = STR_EMPTY_ALLOC();\
    Z_TYPE_P(z) = IS_STRING;  \
  }

Копирование/разделение данных

Так как контейнер zval представляет из себя сложную структуру, в которой хранятся данные разных типов и указатели на внешние данные, например, хэш-таблицы или объекты, которые в свою очередь также могут иметь вложенные указатели на другие данные, то для копирования одной структуры zval в другую без потери данных и связей необходимо использовать специальный конструктор копирования:

void zval_copy_ctor(zval *zvalue)

Конструктор копирования работает непосредственно с переданными данными через парметр zvalue:

zval *dst;  
tmp = *src;

MAKE_STD_ZVAL(src);  
Z_TYPE_P(src) = IS_LONG;  
Z_LVAL_P(src) = 150;

dst = src;

zval_copy_ctor(dst);  
INIT_PZVAL(dst);

При использовании конструктора копирования счетчик ссылок (refcount__gc) и флаг (is_ref__gc), указывающий на то является ли данная структура ссылкой на другие данные, не изменяются. Т.е если предположить, что в примере на данные src ссылаются другие данные(refcount__gc > 1) или же src является ссылкой на другие данные (is_ref__gc = = 1), после копирования данных в контейнере dst значения is_ref__gc и refcount__gc будут такими же как и в src. Для сброса счетчика и флага в примере используется макрос INIT_PZVAL()

Если в этом примере опустить вызов конструктора копирования, то dst и src будут указывать на одну и туже область памяти, т.е. dst будет дополнительным указателем на те же самые данные. При изменении данных, на которые указывает src, dst также будет изменено.

Еще один пример поясняющий работу конструктора копирования:

PHP_FUNCTION(test_zval_ctor) {  
    zval *parameter;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &parameter) == FAILURE)
        return;
    }

  // Изменяем данные
    Z_TYPE_P(parameter) = IS_LONG;
    Z_LVAL_P(parameter) = 150;

    *return_value = *parameter;
    zval_copy_ctor(return_value);
    INIT_PZVAL(return_value);
}

Если в этом примере опустить вызов конструктора копирования и если в функцию параметр передается по ссылке, то изменение данных в zval контейнере parameter повлечет изменение и внешних данных, ссылка на которые была передана в функцию. Если изменения над переданными в функцию данными должны быть локальными, то вначале необходимо их скопировать.

Помимо конструктора копирования zval_copy_ctor() в Zend Engine есть еще несколько макросов копирования/разделения данных, объявленых в Zend/zend.h:

SEPARATE_ZVAL(zval **ppzv) - дублирует данные во временную структуру, является оберткой над конструктором копирования zval_copy_ctor(). При использовании данного макроса копирование данных во временную структуру произойдет только когда счетчик ссылок refcount__gc больше 1. Т.е макрос разделяет zval контейнеры. После разделения счетчик ссылок refcount__gc и флаг is_ref__gc временной структуре будет инициализированы значением 1 и 0 соответственно, а счетчик ссылок контейнера ppzv будет уменьшен на еденицу

SEPARATE_ZVAL_IF_NOT_REF(zval **ppzv) - данный макрос выполняет ту же функцию, что и SEPARATE_ZVAL() с тем отличием, что zval контейнеры будут разделены только тогда, когда они не являются ссылкой на другие данные, т.е. is_ref__gc == 0

SEPARATE_ZVAL_TO_MAKE_IS_REF(zval **ppzv) - данный макрос выполняет ту же функцию, что и SEPARATE_ZVAL_IF_NOT_REF(), но после копирования/разделения устанавливает флаг is_ref__gc в значение 1, т.е делает временную структуру ссылкой

MAKE_COPY_ZVAL(zval **ppzv, zval *pzv) - данный макрос доступен начиная с версии PHP 5.3.5 и является оберткой над zval_copy_ctor(). После копирования данных во временную структуру вызывает макрос INIT_PZVAL(pzv), который устанавливает is_ref__gc в значение 0, а элемент refcount__gc в значение 1. Использование данного макроса эквивалентно:

dst = **src;  
zval_copy_ctor(dst);  
INIT_PZVAL(dst);  

Уничтожение данных

Когда счетчик ссылок достигнет нуля, внутренние структуры контейнера zval будут уничтожены автоматически с помощью вызова деструктора zval_dtor(zval *zval_ptr), после чего будет вызвана функция efree(*zval_ptr) для уничтожения самого zval контейнера

zval_dtor(zval *zval_ptr) - деструктор данных

При вызове деструктора данных, происходит освобождение внутренних структур которые связанны с zval контейнером zval_ptr. К таким структурам относятся массивы (хэш-таблицы), объекты, ресурсы.

Иногда бывает необходимо уничтожить данные "вручную", такая потребность часто возникает при использовании временных zval контейнеров внутри функций, которые не возвращаются из функций как результат. Для этого нужно вызвать zval_dtor(), а затем, для уничтожения самого zval-контейнера - FREE_ZVAL(zval *zval_ptr):

zval *var;

/* Выделяем память и инициализируем zval контейнер */
MAKE_STD_ZVAL(var);

ZVAL_STRING(var, "my string", 1);

/* .... */

zval_dtor(var);  
FREE_ZVAL(var);  

Если необходимо уничтожить переменную зарегистрированную в таблице символов, то вместо zval_dtor() необходимо воспользоваться функцией zend_hash_del():

zval *var;

/* Выделяем память и инициализируем zval контейнер */
MAKE_STD_ZVAL(var);

ZVAL_STRING(var, "my string", 1);

ZEND_SET_SYMBOL(EG(active_symbol_table), "var", var);

/* .... */

zend_hash_del( EG(active_symbol_table), "var", sizeof("var"));  
FREE_ZVAL(var);  
);
Anton Dobkin
Author

Anton Dobkin

Comments