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 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.
php中文网 Courses
php中文网's platform for the latest courses and technical articles, helping PHP learners advance quickly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.