antonio's blog

antonio's blog


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

antonio
Author

Share


Tags


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

antonioantonio

Для получения данных из структуры zval, Zend Engine предоставляет набор макросов.

Для получения данных простого, скалярного типа имеются макросы: Z_BVAL, Z_LVAL, Z_DVAL

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

Для получения строковых данных из структуры zval используется сразу пара макросов Z_STRVAL, Z_STRLEN. Эта особенность связанна с тем, что строковые данные хранятся во вложенной структуре, c двумя элементами, элемента value структуры zval:

struct {  
    char *val;
    int len;
} str;

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

Для безопасного вывода строковых данных структуры zval необходимо использовать макрос ZEND_WRITE(), вместо функции zend_printf()

Для получения массива необходимо использовать макрос Z_ARRVAL().

Для получения доступа к ресурсам необходимо использовать макрос Z_RESVAL(), который возвращает номер ресурса. Ресурсы в zval хранятся в виде простых, целых числе – так называемый номер ресурса. Номер ресурса возвращаемый макросами Z_RESVAL() должен передаваться в функцию zend_fetch_resource(), которая ищет зарегистрированный ресурс по его номеру.

Для получения доступа к объектам используется несколько различных макросов: Z_OBJVAL(), Z_OBJ_HANDLE(), Z_OBJ_HT(), Z_OBJCE(), Z_OBJ_HANDLER(), Z_OBJPROP(). Такое множество макросов для доступа к объектам обусловлено тем, что объекты в PHP представляют из себя сложные структуры данных. Работа с массивами, ресурсами и объектами будет рассмотрена в отдельно.

Также существуют макросы версии макросов с суффиксом _P и _PP, назначение этих суффиксов точно такое же как и в случае с макросом Z_TYPE.

Пример получения данных:

void print_var_value(zval *varv)  
{
  switch( Z_TYPE_P(var) {
    case IS_BOOL:
      zend_printf("Type bool. Value of variable: %s\n",
          Z_BVAL_P(var) ? "true" : "false") );
      break;
    case IS_LONG:
      zend_printf("Type long. Value of variable: %lu\n",
          Z_LVAL_P(var));
      break;
    case IS_DOUBLE:
      zend_printf("Type double. Value of variable: %lu\n",
          Z_DVAL_P(var));
      break;
    case IS_STRING:
      zend_printf("Type double. Value of variable: ");
      ZEND_WRITE(Z_STRVAL_P(var), Z_STRLEN_P(var));
      zend_printf("\n");
      break;
  }
}

Подсчет ссылок

Элемент refcount__gc структуры zval содержит число ссылок (число переменных) на данную структуру, а элемент is_ref__gc – это флаг который указывает является ли данная структура ссылкой. Данные поля используются сборщиком мусора Zend Engine. Поясню назначение данных элементов структуры на примерах:

< ?php  
    $a = 100;
    $b = "string";
?>

В данном фрагменте кода объявляется и инициализируются две переменные. Внутри, за кулисами, Zend Engine создает две структуры zval в каждой из которых элементы refcount__gc и is_ref__gc будут иметь значения:

refcount__gc = 1  
is_ref__gc = 0  

refcount__gc имеет значение 1 так как на данную структуру ссылается одна переменная; is_ref__gc имеет значение 0 так как переменная не является ссылкой на другую структуру zval (переменную)

< ?php  
   $a = 100;
   $b = $a;
?>

Zend Engine создает одну структуру zval в которой элементы refcount__gc и is_ref__gc будут иметь значения:

refcount__gc = 2  
is_ref__gc = 0  

refcount__gc имеет значение 2 так как на данную структуру ссылается две переменные - $a и $b; is_ref__gc имеет значение 0 так как переменная не является ссылкой на другую cтруктуру zval.

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

< ?php  
   $a = 100;
   $b = $a;

   $a = 40;
?>

В данном случае, когда переменной $a будет присвоено новое значение, Zend Engine разделит структуру на две (создаст вторую структуру zval для переменной $b) в каждой из которых элементы refcount__gc и is_ref__gc будут иметь значения:

refcount__gc = 1  
is_ref__gc = 0  
< ?php  
   $a = 100;
   $b = &a;
?>

Zend Engine создает одну структуру zval в которой элементы refcount__gc и is_ref__gc будут иметь значения:

refcount__gc = 2  
is_ref__gc = 1  

refcount__gc имеет значение 2 так как на данную структуру ссылается две переменные; is_ref__gc имеет значение 1 так как обе переменные является ссылкой на одну структуру zval

При изменении значения одной из переменных значение второй, соответственно, также изменится. Разделение в данном случае не произойдет, так как явно указано, что переменные являются ссылками на одну структуру zval.

Если уничтожить одну из переменных:

< ?php  
   unset($a)
?>

то значения refcount__gc и is_ref__gc будут изменены:

refcount__gc = 1  
is_ref__gc = 0  

Данные в памяти уничтожены не будут. Как только refcount__gc будет иметь значение 0 это станет сигналом сборщику мусора освободить память занимаемую под структуру zval, так как нет переменных в пользовательском пространстве ссылающихся на нее, данные больше не нужны.

Для манипуляции счетчиками ссылок Zend Engine предоставляет следующие макросы:

Выше перечисленные макросы объявлены в файле Zend/zend.h:

#define Z_REFCOUNT_PP(ppz)      Z_REFCOUNT_P(*(ppz))
#define Z_SET_REFCOUNT_PP(ppz, rc)  Z_SET_REFCOUNT_P(*(ppz), rc)
#define Z_ADDREF_PP(ppz)        Z_ADDREF_P(*(ppz))
#define Z_DELREF_PP(ppz)        Z_DELREF_P(*(ppz))
#define Z_ISREF_PP(ppz)         Z_ISREF_P(*(ppz))
#define Z_SET_ISREF_PP(ppz)     Z_SET_ISREF_P(*(ppz))
#define Z_UNSET_ISREF_PP(ppz)       Z_UNSET_ISREF_P(*(ppz))
#define Z_SET_ISREF_TO_PP(ppz, isref)   Z_SET_ISREF_TO_P(*(ppz), isref)

#define Z_REFCOUNT_P(pz)        zval_refcount_p(pz)
#define Z_SET_REFCOUNT_P(pz, rc)    zval_set_refcount_p(pz, rc)
#define Z_ADDREF_P(pz)          zval_addref_p(pz)
#define Z_DELREF_P(pz)          zval_delref_p(pz)
#define Z_ISREF_P(pz)           zval_isref_p(pz)
#define Z_SET_ISREF_P(pz)       zval_set_isref_p(pz)
#define Z_UNSET_ISREF_P(pz)     zval_unset_isref_p(pz)
#define Z_SET_ISREF_TO_P(pz, isref) zval_set_isref_to_p(pz, isref)

#define Z_REFCOUNT(z)           Z_REFCOUNT_P(&(z))
#define Z_SET_REFCOUNT(z, rc)       Z_SET_REFCOUNT_P(&(z), rc)
#define Z_ADDREF(z)         Z_ADDREF_P(&(z))
#define Z_DELREF(z)         Z_DELREF_P(&(z))
#define Z_ISREF(z)          Z_ISREF_P(&(z))
#define Z_SET_ISREF(z)          Z_SET_ISREF_P(&(z))
#define Z_UNSET_ISREF(z)        Z_UNSET_ISREF_P(&(z))
#define Z_SET_ISREF_TO(z, isref)    Z_SET_ISREF_TO_P(&(z), isref)

#define PZVAL_IS_REF(z)     Z_ISREF_P(z)

#define INIT_PZVAL(z)       \
    (z)->refcount__gc = 1;  \
    (z)->is_ref__gc = 0;


static zend_always_inline zend_uint zval_refcount_p(zval* pz) {  
    return pz->refcount__gc;
}

static zend_always_inline zend_uint zval_set_refcount_p(zval* pz, zend_uint rc) {  
    return pz->refcount__gc = rc;
}

static zend_always_inline zend_uint zval_addref_p(zval* pz) {  
    return ++pz->refcount__gc;
}

static zend_always_inline zend_uint zval_delref_p(zval* pz) {  
    return --pz->refcount__gc;
}

static zend_always_inline zend_bool zval_isref_p(zval* pz) {  
    return pz->is_ref__gc;
}

static zend_always_inline zend_bool zval_set_isref_p(zval* pz) {  
    return pz->is_ref__gc = 1;
}

static zend_always_inline zend_bool zval_unset_isref_p(zval* pz) {  
    return pz->is_ref__gc = 0;
}

static zend_always_inline zend_bool zval_set_isref_to_p(zval* pz, zend_bool isref) {  
    return pz->is_ref__gc = isref;
}
antonio
Author

antonio

Comments