PHP 是弱類型語言,但底層由 C 實現(強類型)。所有 PHP 變數在內核中統一使用 zval 結構體存儲,包含值、類型、引用計數、是否引用四大欄位。本文詳細解析 zval、zvalue_value 結構,以及標量、陣列、物件、資源等類型的存儲方式。
// Zend/zend.h
typedef struct _zval_struct zval;
struct _zval_struct {
zvalue_value value; // 變數實際值(聯合體)
zend_uint refcount__gc; // 引用計數(GC)
zend_uchar type; // 類型標識
zend_uchar is_ref__gc; // 是否為引用
};
欄位說明:
value:聯合體 zvalue_value,根據 type 解釋不同欄位。refcount__gc:引用計數,初始為 1。PHP 5.3 引入 GC 後改名(舊版為 refcount)。type:IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT、IS_RESOURCE、IS_CONSTANT、IS_CONSTANT_ARRAY。is_ref__gc:是否為引用(&),初始 0。typedef union _zvalue_value {
long lval; // 整數、布爾
double dval; // 浮點數
struct {
char *val; // 字串指標
int len; // 字串長度
} str;
HashTable *ht; // 陣列
zend_object_value obj; // 物件
} zvalue_value;
value.lval,type = IS_LONG/IS_BOOL。宏 ZVAL_LONG(z, 123)、ZVAL_BOOL(z, 1)。value.dval,type = IS_DOUBLE。宏 ZVAL_DOUBLE(z, 3.14)。type = IS_NULL。宏 ZVAL_NULL(z)。本質是整數句柄,存入 value.lval,type = IS_RESOURCE。宏 ZVAL_RESOURCE(z, handle)。實際資源結構體存放在全局資源表 EG(regular_list) 中。
使用 value.str 結構:val 指向字串記憶體,len 記錄長度(不含結束符)。為何存長度? 字串操作極高頻,預存長度避免每次 strlen() 掃描,空間換時間。
value.ht 指向 HashTable。PHP 陣列底層是有序哈希表,支援整數/字串鍵混合、遍歷順序即插入順序。核心結構:Bucket(鍵值節點)+ HashTable(桶陣列+雙向鏈表)。
typedef struct _zend_object_value {
zend_object_handle handle; // 無符號整數,物件池索引
zend_object_handlers *handlers; // 運算符處理函數表
} zend_object_value;
物件僅在運行時創建。handle 是全局物件池 EG(objects_store) 的索引;handlers 定義屬性讀寫、方法調用、複製、析構等行為。
PHP 變數不聲明類型,運行時由 zval.type 動態標識。同一變數可先存整數、後存字串,只需修改 type 與 value 對應欄位。這就是「容器兼容並包」的弱類型實現:統一容器 + 類型標籤 + 聯合體值。
$b = $a 時,zval.refcount++,雙方指向同一 zval。$b = 2 時,檢測 refcount > 1,分離新 zval(Copy-on-Write),refcount--。unset($a) 遞減 refcount,歸零時釋放記憶體。