PHP 作為弱型別語言,其核心在於統一的變數存儲結構 zval。本文深入剖析 zval、zvalue_value、HashTable、Bucket 等核心結構,揭示 PHP 變數實現的底層邏輯。
$ 開頭,字母/底線/數字,區分大小寫;支援變數變數 $$a。| 類別 | 類型 |
|---|---|
| 標量 | Boolean |
| Integer | |
| Float (Double) | |
| String | |
| 複合 | Array |
| Object | |
| 特殊 | NULL |
| Resource |
zvalstruct _zval_struct {
zvalue_value value; // 實際值(union)
zend_uint refcount_gc; // 引用計數(GC)
zend_uchar is_ref_gc; // 是否為引用
zend_uchar type; // 類型標識
};
zvalue_value 使用 union 節省記憶體,同一時刻只佔用一個成員空間:
typedef union _zvalue_value {
long lval; // 整數、布爾、資源 ID
double dval; // 浮點數
struct { char *val; int len; } str; // 字串
HashTable *ht; // 陣列
zend_object_value obj; // 物件
} zvalue_value;
PHP 內部大量使用 HashTable:變數作用域、函數表、類屬性/方法、陣列等。
typedef struct _hashtable {
uint nTableSize; // 桶數量(2 的冪,最小 8)
uint nTableMask; // nTableSize - 1,用於位運算取模優化
uint nNumOfElements; // 當前元素數(count() 直接返回)
ulong nNextFreeElement; // 下一個數字索引位置
Bucket *pInternalPointer; // 遍歷指針(foreach 效能優勢來源)
Bucket *pListHead; // 雙向鏈表頭
Bucket *pListTail; // 雙向鏈表尾
Bucket **arBuckets; // 桶陣列
dtor_func_t pDestructor; // 元素銷毀回調
zend_bool persistent; // 記憶體分配方式
unsigned char nApplyCount; // 遞歸訪問計數
zend_bool bApplyProtection; // 遞歸保護(最多 3 層)
} HashTable;
索引計算:index = hash_value & nTableMask。因 nTableSize 為 2 的冪,nTableMask 二進位全為 1,& 操作比 % 快數倍。擴容時維持 2 的冪特性。
typedef struct bucket {
ulong h; // Key 哈希值(或數字索引)
uint nKeyLength; // Key 長度(數字索引為 0)
void *pData; // 值指針(通常為副本)
void *pDataPtr; // 若為指針資料,指向真實值
Bucket *pListNext; // 全域雙向鏈表下一個
Bucket *pListLast; // 全域雙向鏈表上一個
Bucket *pNext; // 同桶鏈表下一個(衝突鏈)
Bucket *pLast; // 同桶鏈表上一個
char arKey[1]; // 變長結構體技巧:儲存實際 Key 字串
} Bucket;
arKey[1] 為柔性陣列技巧:分配時 sizeof(Bucket) - 1 + nKeyLength,實現 Key 與 Bucket 一體化儲存,減少指針跳躍。
index = h & nTableMask 定位桶pNext/pLast)pListNext/pListLast)保證遍歷順序2x 擴容並重新雜湊zend_object_valuetypedef struct _zend_object_value {
zend_object_handle handle; // 物件唯一 ID
const zend_object_handlers *handlers; // 虛函數表
} zend_object_value;
實際物件資料儲存於物件存儲,通過 handle 索引查找。
zval.type 指示 zvalue_value 活躍成員,實現弱型別。refcount_gc + is_ref_gc 管理記憶體生命週期。理解這些結構,有助於編寫高效 PHP 代碼、開發擴展、排查疑難效能問題。