首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 內核原始碼分析:變數儲存機制與 zval 結構深度解析

     編輯:PHP 綜合知識

從 PHP 程式碼到 C 內核:變數賦值的完整路徑

// 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);

核心結構體定義 (PHP 7.x/8.x 簡化版)

// 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;

關鍵宏展開與記憶體管理

MAKE_STD_ZVAL (PHP 5 風格,PHP 7+ 已改用 zval 直接分配)

// 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);

ZVAL_LONG - 設定長整數值

#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;       // 實際數值

ZEND_SET_SYMBOL - 符號表註冊

#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 7/8 重大變更:zval 值語義與引用計數

特性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 (引用計數)

實際執行流程範例:$a = 1; $b = &$a; $b = 2;

// 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

記憶體管理關鍵點

  • zend_string:字串結構體包含長度、雜湊值、引用計數、實際字元資料 (柔性陣列)。IS_STR_INTERNED 標記常駐字串 (編譯時常數),不計入引用計數。
  • zend_array:HashTable 實現,Bucket 陣列儲存 key-value,支援整數/字串鍵,自動擴容。
  • GC (Garbage Collection):針對循環引用 (陣列/物件互相引用) 的標記-清除演算法,gc_collect_cycles() 觸發。
  • OPcache:編譯後的 opcode、常數、類別定義儲存於共享記憶體,interned_strings 跨進程共用。

除錯技巧:PHP 內建函式觀察 zval

$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 擴展、除錯核心問題、優化效能的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved