Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions tip_dannih_zval/bazovaya_struktura.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ typedef union _zvalue_value {

Для хранения чисел PHP представляет 2 типа: `IS_LONG` и `IS_DOUBLE`, которые используют члены `long lval` и `double dval` соответственно. Первый используется для хранения целых чисел, второй — для чисел с плавающей точкой.

Есть несеколько вещей, которые вам следует знать о типе данных `long`. Во-первых, это signed integer, то есть он может содержать положительные и отрицательные значения, но этот тип данных не подходит для побитовых операций. Во-вторых, `long` имеет разные размеры на разных платформах: на 32-битных системах он имеет размер 32 бита или 4 байта, но на 64-битных системах он может иметь размер как 4, так и 8 байт. В Unix-системах он обычно имеет размер в 8 байт, в то время как в 64-битных версиях Windows использует только 4 байта.
Есть несколько вещей, которые вам следует знать о типе данных `long`. Во-первых, это signed integer, то есть он может содержать положительные и отрицательные значения, но этот тип данных не подходит для побитовых операций. Во-вторых, `long` имеет разные размеры на разных платформах: на 32-битных системах он имеет размер 32 бита или 4 байта, но на 64-битных системах он может иметь размер как 4, так и 8 байт. В Unix-системах он обычно имеет размер в 8 байт, в то время как в 64-битных версиях Windows использует только 4 байта.

По этой причине вы не должны полагаться на конкретное значение типа `long`. Минимальное и максимальное значения, которые могут быть сохранены в типе данных `long` доступны в константах `LONG_MIN` и `LONG_MAX` и размер этого типа может быть определен с использованием макро `SIZEOF_LONG` (в отличии от `sizeof(long)` этот макро может быть использован и в `#if` директивах).
По этой причине вы не должны полагаться на конкретное значение типа `long`. Минимальное и максимальное значения, которые могут быть сохранены в типе данных `long` доступны в константах `LONG_MIN` и `LONG_MAX` и размер этого типа может быть определен с использованием макроса `SIZEOF_LONG` (в отличии от `sizeof(long)` этот макрос может быть использован и в `#if` директивах).

Тип данных `double` предназначен для хранения чисел с плавающей точкой и, обычно, следуя спецификации IEEE-754, он имеет размер в 8 байт. Детали этого формата не будут обсуждаться здесь, но вам как минимум следует знать, что этот тип имеет ограниченную точность и часто хранит не точно то значение, на которое вы рассчитываете.

Булевы переменные используют флаг `IS_BOOL` и хранятся в поле `long val` как значения `0` (false) и `1` (true). Так как этот тип использут только 2 значения, то, теоретически, достаточно было использовать тип меньшего размера (например `zend_bool`), но так как `zvalue_value` — это юнион и под него и так выделен объем памяти соответствующий самому большому члену данных, то применение более компактной переменной для булевых значений не приведет к экономии памяти. Поэтому `lval` повторно использован в этом случае.

Строки (`IS_STRING`) хранятся в структуре `struct {char *val; int len; } str;`, то есть строка хранится как указатель на строку `char *` и целочисленная длина строки `int`. Строки в PHP должны явно хранить свою длину для того чтобы иметь возможность содержать NUL байты (`\0`) и быть бинарно безопасными (binary safe). Но несмотря на это, строки используемые в PHP все равно заканчиваются нулевым байтом (NUL-terminated), чтобы обеспечить совместимость с библиотечными функциями, которые не принимают аргумент с длиной строки, а ожидают найти нулевой байт в конце строки. Конечно, в таких случаях строки больше не могут быть бинарно безопасными и будут обрезаны до первого вхождения нулевого байта. Например, много функций связанных с файловой системой и большинство строковых функций из libc ведут себя подобным образом.

Длина строки измеряется в байтах (не числом Unicode-символов) и не должно включать нулевой байт, то есть длина строки `foo` равна 3, несмотря на то, что для её хранения используется 4 байта. Если вы оперделяете длину строки с использованием `sizeof` вам нужно вычитать единицу: `strlen("foo") == sizeof("foo") - 1`.
Длина строки измеряется в байтах (не числом Unicode-символов) и не должно включать нулевой байт, то есть длина строки `foo` равна 3, несмотря на то, что для её хранения используется 4 байта. Если вы определяете длину строки с использованием `sizeof` вам нужно вычитать единицу: `strlen("foo") == sizeof("foo") - 1`.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😆


Очень важно понимать: длина строки хранится в типе `int`, а не в `long` или каком-то другом похожем типе. Это исторический артефакт, который ограничивает длину строки 2147483647 байтами (2 гигабайта). Строки большего размера будут причиной переполнения (что сделает их длину отрицательной).

Expand Down Expand Up @@ -95,7 +95,7 @@ if (Z_TYPE_P(zv_ptr) == IS_LONG) {
php_printf("Zval is a long with value %ld\n", Z_LVAL_P(zv_ptr));
} else /* ... */
```
Код выше использует макро `Z_TYPE_P` для получения метки типа и `Z_LVAL_P` для получения целочисленного значения. Все макросы доступа имеют варианты с суффиксами `_P`, `_PP` и без суффикса. Который из них использовать зависит от того с чем вы работаете: `zval`, `zval*` или `zval**`:
Код выше использует макрос `Z_TYPE_P` для получения метки типа и `Z_LVAL_P` для получения целочисленного значения. Все макросы доступа имеют варианты с суффиксами `_P`, `_PP` и без суффикса. Который из них использовать зависит от того с чем вы работаете: `zval`, `zval*` или `zval**`:
```c
zval zv;
zval *zv_ptr;
Expand All @@ -107,7 +107,7 @@ Z_TYPE_P(zv_ptr); // = zv_ptr->type
Z_TYPE_PP(zv_ptr_ptr); // = (*zv_ptr_ptr)->type
Z_TYPE_PP(*zv_ptr_ptr_ptr); // = (**zv_ptr_ptr_ptr)->type
```
Число букв `P` в суффиксе должно быть равно числу `*` в типе. Это правило работает только до `zval**`, то есть нет специального макро для работы с `zval***` так как на практике такие указатели встречаются редко.
Число букв `P` в суффиксе должно быть равно числу `*` в типе. Это правило работает только до `zval**`, то есть нет специального макроса для работы с `zval***` так как на практике такие указатели встречаются редко.

Аналогично `Z_LVAL` существуют макросы для извлечения значений всех других типов. Для демонстрации их использования давайте создадим простую функцию выводящую значение zval-а:
```c
Expand Down Expand Up @@ -191,22 +191,22 @@ PHP_FUNCTION(hello_world) {
```
Исполнение в консоли команды `php -r "echo hello_world();"` должно напечатать в терминале `hello world!`.

В этом примере мы устанавливаем значение переменной `return_value`, которая имеет тип `zval*` и определена в макро `PHP_FUNCTION`. Детальнее мы рассмотрим эту переменную в следующей главе, сейчас нам достаточно знать, что значение этой переменной будет значением, возвращаемым функцией. По умолчанию эта переменная инициализируется типом `IS_NULL`.
В этом примере мы устанавливаем значение переменной `return_value`, которая имеет тип `zval*` и определена в макросе `PHP_FUNCTION`. Детальнее мы рассмотрим эту переменную в следующей главе, сейчас нам достаточно знать, что значение этой переменной будет значением, возвращаемым функцией. По умолчанию эта переменная инициализируется типом `IS_NULL`.

Установка значения zval-ов с помощью макросов очень просто, но есть вещи, о которых нужно помнить. Во-первых, вы должны помнить, что метка типа определяет тип zval-а. Недостаточно просто установить значение (через `Z_STRVAL` и `Z_STRLEN` в этом примере), вам также нужно установить метку типа (type tag).

Кроме того, вы должны понимать, что в большинстве случаев zval "владеет" своим значением и zval имеет более долгую жизнь чем область видимиости, в которой вы устанавливаете его значение. Иногда это не так, когда вы работаете с временным zval-ами, но в большинстве случаев это так.
Кроме того, вы должны понимать, что в большинстве случаев zval "владеет" своим значением и zval имеет более долгую жизнь чем область видимости, в которой вы устанавливаете его значение. Иногда это не так, когда вы работаете с временным zval-ами, но в большинстве случаев это так.

В контексте приведенного выше примера это значит, что переменная `return_value` будет существовать и после выхода за пределы тела нашей функции (что очевидно, так как иначе никто не сможет использовать значение возвращенное функцией), поэтому нельзя использовать временные значения в функциях. Например, нельзя просто написать `Z_STRVAL_P(return_value) = "hello world!"`, так как строковый литерал "hello world!" перестанет существовать после выхода из тела функции (что истинно для всех значений размещенных в стэке в C).

По этой причине мы должны скопировать строку используя `estrdup()`. Вызов этой функции создаст отдельную копию строки в куче. Так как zval “владеет” своим значением, он сам освободит память выделенную под эту копию когда zval будет уничтожен. Это также применимо к любым другим "комплексным" значениям zval-ов. Например, если вы устанавливаете `HashTable*` для массива, то zval станет его владельцем и освободит когда zval будет уничтожен. При использовании примитивных типов данных, таких как числа, вам не нужно об этом заботиться, так как они и так всегда копируются.

Последнее на что нужно обратить внимание: не все макросы доступа возвращают член-данных. Например, макро `Z_BVAL` определен так:
Последнее на что нужно обратить внимание: не все макросы доступа возвращают член-данных. Например, макрос `Z_BVAL` определен так:
```c
#define Z_BVAL(zval) ((zend_bool)(zval).value.lval)
```

Так как этот макро содержит присвоение типа (type casting) вы не сможете присвоить значение `Z_BVAL_P(return_value) = 1`. Это единственное исключение, кроме нескольких макросов связанных с объектами. Остальные макросы могут быть использованы для присвоения значений.
Так как этот макрос содержит присвоение типа (type casting) вы не сможете присвоить значение `Z_BVAL_P(return_value) = 1`. Это единственное исключение, кроме нескольких макросов связанных с объектами. Остальные макросы могут быть использованы для присвоения значений.

На практике вам не нужно беспокоиться об этом ограничении. Так как присвоение значения это частая задача, PHP предоставляет другой набор макросов для этих целей. Они позволяют вам одновременно задавать метку типа и значение. Предыдущий пример можно переписать так:
```c
Expand All @@ -215,20 +215,20 @@ PHP_FUNCTION(hello_world) {
}
```

Очень часто строка должна быть скопирована при присваивании zval-у и последний парметр (типа boolean) в макро `ZVAL_STRINGL` поможет вам в этом. Если вы передадите `0` — строка будет использована как есть, но если вы передадите `1`, то она будет скоприована с помощью `estrndup()`. Наш пример может быть переписан так:
Очень часто строка должна быть скопирована при присваивании zval-у и последний параметр (типа boolean) в макросе `ZVAL_STRINGL` поможет вам в этом. Если вы передадите `0` — строка будет использована как есть, но если вы передадите `1`, то она будет скоприована с помощью `estrndup()`. Наш пример может быть переписан так:
```c
PHP_FUNCTION(hello_world) {
ZVAL_STRINGL(return_value, "hello world!", strlen("hello world!"), 1);
}
```

Более того, нам не нужно вручную рассчитывать длину строки, вместо этого мы можем использовать макро `ZVAL_STRING` (без `L` в конце):
Более того, нам не нужно вручную рассчитывать длину строки, вместо этого мы можем использовать макрос `ZVAL_STRING` (без `L` в конце):
```c
PHP_FUNCTION(hello_world) {
ZVAL_STRING(return_value, "hello world!", 1);
}
```
Если вам известна длина строки (например она была передана вам кем-то), то вам всегда следует использовать её через макро `ZVAL_STRINGL` для обеспечения бинарной безопасности. Если вы не знаете длину строки (или вы уверены, что она не содержит нулевые байты, например при использовании литералов), то вы можете использовать `ZVAL_STRING`.
Если вам известна длина строки (например она была передана вам кем-то), то вам всегда следует использовать её через макрос `ZVAL_STRINGL` для обеспечения бинарной безопасности. Если вы не знаете длину строки (или вы уверены, что она не содержит нулевые байты, например при использовании литералов), то вы можете использовать `ZVAL_STRING`.

Кроме `ZVAL_STRING(L)` есть еще несколько макросов для установки значений, они перечислены в следующем примере:
```c
Expand Down