Backend Development 8 min read

Understanding the PHP zval Structure in the Source Code

This article examines how PHP stores variables internally by analyzing the zval structure defined in the C source code, detailing its fields, type flags, memory layout, and associated macros, and explains why each variable occupies 16 bytes at runtime.

php中文网 Courses
php中文网 Courses
php中文网 Courses
Understanding the PHP zval Structure in the Source Code

PHP variables are dynamically typed, allowing a variable such as $a to change its type at runtime. The PHP engine stores these variables in a C struct called zval , which is defined in php-src/Zend/zend_types.h .

The zval struct contains a zend_value union that holds the actual value and a set of flags that describe the type (e.g., IS_LONG for integers, IS_STRING for strings). The type information is stored in u1.v.type .

Below is a side‑by‑side view of the original source and a simplified translation:

<code>typedef struct _zval_struct zval;</code>
<code>...</code>
<code>-------------------------------------------------------------------------------------------</code>
<code>struct _zval_struct {</code>
<code>    zend_value value;                               |     zend_value value;</code>
<code>    union {                                         |     union {</code>
<code>        struct {                                    |         struct {</code>
<code>            ZEND_ENDIAN_LOHI_3(                     |             unsigned char type;</code>
<code>                zend_uchar type,                    |             unsigned char type_flags;</code>
<code>                zend_uchar type_flags,              |             union {</code>
<code>                union {                             |                 unsigned short extra;</code>
<code>                    uint16_t extra;                 |             } u;</code>
<code>                } u                                 |         } v;</code>
<code>            )                                       |         unsigned int type_info;</code>
<code>        } v;                                        |     } u1;</code>
<code>        uint32_t type_info;                         |     union {</code>
<code>    } u1;                                           |         unsigned int next;</code>
<code>    union {                                         |         unsigned int cache_slot;</code>
<code>        uint32_t next;                              |         unsigned int opline_num;</code>
<code>        uint32_t cache_slot;                        |         unsigned int lineno;</code>
<code>        uint32_t opline_num;                        |         unsigned int num_args;</code>
<code>        uint32_t lineno;                            |         unsigned int fe_pos;</code>
<code>        uint32_t num_args;                          |         unsigned int fe_iter_idx;</code>
<code>        uint32_t fe_pos;                            |         unsigned int access_flags;</code>
<code>        uint32_t fe_iter_idx;                       |         unsigned int property_guard;</code>
<code>        uint32_t access_flags;                      |         unsigned int constant_flags;</code>
<code>        uint32_t property_guard;                    |         unsigned int extra;</code>
<code>        uint32_t constant_flags;                    |     } u2;</code>
<code>        uint32_t extra;                             | };</code>
<code>    } u2;                                           |</code>
<code>};                                                 |</code>

The zend_value union is defined as follows, with each member representing a possible PHP value type:

<code>typedef union _zend_value {</code>
<code>    zend_long         lval; /* long value */</code>
<code>    double            dval; /* double value */</code>
<code>    zend_refcounted  *counted;</code>
<code>    zend_string      *str;</code>
<code>    zend_array       *arr;</code>
<code>    zend_object      *obj;</code>
<code>    zend_resource    *res;</code>
<code>    zend_reference   *ref;</code>
<code>    zend_ast_ref     *ast;</code>
<code>    zval             *zv;</code>
<code>    void             *ptr;</code>
<code>    zend_class_entry *ce;</code>
<code>    zend_function    *func;</code>
<code>    struct {</code>
<code>        uint32_t w1;</code>
<code>        uint32_t w2;</code>
<code>    } ww;</code>
<code>} zend_value;</code>

Because a union occupies the size of its largest member, zend_value is 8 bytes on typical 64‑bit platforms. The surrounding fields add another 8 bytes (4 bytes for u1 and 4 bytes for u2 ), so a complete zval occupies 16 bytes.

When a PHP variable holds an integer or a double, the value is stored directly in lval or dval . For strings, arrays, objects, and other complex types, the engine allocates separate memory and stores a pointer in the union (e.g., zval.value.str ).

The type constants are defined in the same header file:

<code>#define IS_UNDEF        0</code>
<code>#define IS_NULL         1</code>
<code>#define IS_FALSE        2</code>
<code>#define IS_TRUE         3</code>
<code>#define IS_LONG         4</code>
<code>#define IS_DOUBLE       5</code>
<code>#define IS_STRING       6</code>
<code>#define IS_ARRAY        7</code>
<code>#define IS_OBJECT       8</code>
<code>#define IS_RESOURCE     9</code>
<code>#define IS_REFERENCE    10</code>
<code>#define IS_CONSTANT_AST 11</code>
<code>#define IS_INDIRECT     13</code>
<code>#define IS_PTR          14</code>
<code>#define IS_ALIAS_PTR    15</code>
<code>#define _IS_ERROR       15</code>
<code>#define _IS_BOOL        16</code>
<code>#define IS_CALLABLE     17</code>
<code>#define IS_ITERABLE     18</code>
<code>#define IS_VOID         19</code>
<code>#define _IS_NUMBER      20</code>

Thus, every PHP variable consumes at least 16 bytes of memory, roughly twice the size of a native C variable, which provides the flexibility PHP developers rely on.

BackendC++PHPmemory layoutzvalInternals
php中文网 Courses
Written by

php中文网 Courses

php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.