// PHP 程式碼 $php_var = 1;
對應 Zend 引擎內核 C 代碼流程:
// 1. 宣告 zval 指標 zval *c_var; // 2. 分配並初始化 zval 結構 MAKE_STD_ZVAL(c_var); // 展開見下文 // 3. 設定值為長整數 1 ZVAL_LONG(c_var, 1); // 4. 註冊到當前作用域符號表 (全域/區域) ZEND_SET_SYMBOL(EG(active_symbol_table), "php_var", c_var);
// zval 結構 (PHP 7 起採用值語義,不再用指標指向 zval)
struct _zval_struct {
zend_value value; // 聯合體:儲存實際資料
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, // 類型標籤 (IS_LONG, IS_STRING, IS_ARRAY...)
zend_uchar type_flags, // 類型旗標
zend_uchar const_flags, // 常數旗標
zend_uchar reserved // 保留
)
} v;
uint32_t type_info; // 合併上述欄位供快速判斷
} u1;
union {
uint32_t next; // HashTable 迭代用
uint32_t cache_slot; // OPcache 快取槽
uint32_t lineno; // 行號 (編譯時)
uint32_t num_args; // 參數數量
uint32_t fe_pos; // foreach 位置
uint32_t fe_iter_idx; // foreach 迭代器索引
} u2;
};
// 實際資料聯合體
typedef union _zend_value {
zend_long lval; // IS_LONG, IS_RESOURCE
double dval; // IS_DOUBLE
zend_refcounted *counted; // 字串、陣列、物件、資源 (引用計數)
zend_string *str;
zend_array *arr;
zend_object *obj;
zend_resource *res;
zend_reference *ref;
zend_ast_ref *ast;
zend_function *func;
void *ptr;
zend_class_entry *ce;
zend_function *fbc; // 快取方法
} zend_value;
// PHP 5 風格 (參考用)
#define MAKE_STD_ZVAL(zv) \
do { \
ALLOC_ZVAL(zv); \
INIT_PZVAL(zv); \
} while (0)
#define ALLOC_ZVAL(z) \
ZEND_FAST_ALLOC(z, zval, ZVAL_CACHE_LIST)
#define ZEND_FAST_ALLOC(p, type, fc_type) \
(p) = (type *) emalloc(sizeof(type))
#define INIT_PZVAL(z) \
(z)->refcount__gc = 1; \
(z)->is_ref__gc = 0;
// PHP 7+ 風格:直接在堆疊或結構體內嵌 zval,不再單獨 emalloc
// 例如:zval val; ZVAL_LONG(&val, 1);
#define ZVAL_LONG(z, l) \
do { \
Z_TYPE_INFO_P(z) = IS_LONG; \
Z_LVAL_P(z) = (l); \
} while (0)
// 展開後:
// (z)->u1.v.type = IS_LONG; // 類型標籤
// (z)->value.lval = 1; // 實際數值
#define ZEND_SET_SYMBOL(symtable, name, var) \
ZEND_SET_SYMBOL_WITH_LENGTH(symtable, name, strlen(name)+1, var, 1, 0)
#define ZEND_SET_SYMBOL_WITH_LENGTH(symtable, name, name_length, var, _refcount, _is_ref) \
do { \
zval **orig_var; \
if (zend_hash_find(symtable, name, name_length, (void**)&orig_var) == SUCCESS \
&& PZVAL_IS_REF(*orig_var)) { \
/* 變數已存在且為引用 */ \
ZVAL_COPY_VALUE(var, *orig_var); \
var->u1.v.type_flags |= IS_REFERENCE; \
if (_refcount) Z_ADDREF_P(var); \
zval_ptr_dtor(orig_var); \
*orig_var = var; \
} else { \
/* 新變數或非引用覆寫 */ \
var->u1.v.type_flags = _is_ref ? IS_REFERENCE : 0; \
if (_refcount) Z_ADDREF_P(var); \
zend_hash_update(symtable, name, name_length, &var, sizeof(zval*), NULL); \
} \
} while (0)
| 特性 | PHP 5 (指標) | PHP 7+ (值) |
|---|---|---|
| zval 儲存 | 堆上分配,指標傳遞 | 堆疊/內嵌結構體,值複製 |
| 引用計數 | zval 本身有 refcount | 僅複雜類型 (字串/陣列/物件) 透過 zend_refcounted 計數 |
| 複製語義 | 複製指標,需手動分離 | 複製 zval 結構體 (16/24 bytes),Copy-on-Write |
| 整數/浮點 | 儲存於 zval->value.lval/dval | 直接儲存於 zend_value 聯合體 |
| 字串/陣列 | 指向獨立結構 | 指向 zend_string/zend_array (引用計數) |
// 1. $a = 1 zval a_val; ZVAL_LONG(&a_val, 1); zend_hash_update(EG(active_symbol_table), "a", &a_val); // 2. $b = &$a (建立引用) zval *a_ptr = zend_hash_find_ptr(EG(active_symbol_table), "a"); zval b_val; ZVAL_REF(&b_val, a_ptr); // b_val.type = IS_REFERENCE, b_val.value.ref = a_ptr zend_hash_update(EG(active_symbol_table), "b", &b_val); // 3. $b = 2 (透過引用修改) zval *b_ptr = zend_hash_find_ptr(EG(active_symbol_table), "b"); zval *target = Z_REFVAL_P(b_ptr); // 取得引用目標 a_ptr ZVAL_LONG(target, 2); // 直接修改 a_val 的值 // 現在 $a 也是 2
IS_STR_INTERNED 標記常駐字串 (編譯時常數),不計入引用計數。gc_collect_cycles() 觸發。interned_strings 跨進程共用。$a = 1; $b = 'hello'; $c = [1,2,3]; $d = new stdClass(); debug_zval_dump($a); // 輸出: long(1) refcount(1) // 注意:debug_zval_dump 參數傳遞會增加 refcount,實際 refcount = 顯示值 - 1 // 更準確的觀察需用擴展 (如 php-vld, xdebug) 或 GDB 搭配 PHP 原始碼
PHP 變數在內核層以 zval 為核心載體,PHP 7+ 採用值語義 + 複雜類型引用計數設計,大幅提升效能並簡化記憶體管理。理解 zval 結構、型別標籤、引用計數、符號表操作,是開發 PHP 擴展、除錯核心問題、優化效能的基石。