PHP7-變量的內部實現

變量的基礎結構

//zend_types.h
typedef struct _zval_struct     zval;

typedef union _zend_value {
    zend_long         lval;    //int整形
    double            dval;    //浮點型
    zend_refcounted  *counted;
    zend_string      *str;     //string字符串
    zend_array       *arr;     //array數組
    zend_object      *obj;     //object對象
    zend_resource    *res;     //resource資源類型
    zend_reference   *ref;     //引用類型,通過&$var_name定義的
    zend_ast_ref     *ast;     //下面幾個都是內核使用的value
    zval             *zv;
    void             *ptr;
    zend_class_entry *ce;
    zend_function    *func;
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

struct _zval_struct {
    zend_value        value; //變量實際的value
    union {
        struct {
            ZEND_ENDIAN_LOHI_4( //這個是為了兼容大小字節(jié)序,小字節(jié)序就是下面的順序,大字節(jié)序則下面4個順序翻轉
                zend_uchar    type,         //變量類型
                zend_uchar    type_flags,  //類型掩碼,不同的類型會有不同的幾種屬性,內存管理會用到
                zend_uchar    const_flags,
                zend_uchar    reserved)     //call info,zend執(zhí)行流程會用到
        } v;
        uint32_t type_info; //上面4個值的組合值,可以直接根據type_info取到4個對應位置的值
    } u1;
    union {
        uint32_t     var_flags;
        uint32_t     next;                 //哈希表中解決哈希沖突時用到
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
    } u2; //一些輔助值
};

zval結構比較簡單,內嵌一個union類型的zend_value保存具體變量類型的值或指針,zval中還有兩個union:u1、u2:

u1: 它的意義比較直觀,變量的類型就通過u1.v.type區(qū)分,另外一個值type_flags為類型掩碼,在變量的內存管理、gc機制中會用到(之前分享的垃圾回收機制中,變量的type_flags只有包含IS_TYPE_COLLECTABLE的變量才會被GC收集)
u2: 這個值純粹是個輔助值,假如zval只有:value、u1兩個值,整個zval的大小也會對齊到16byte,既然不管有沒有u2大小都是16byte,把多余的4byte拿出來用于一些特殊用途還是很劃算的,比如next在哈希表解決哈希沖突時會用到,還有fe_pos在foreach會用到......
從zend_value可以看出,除long、double類型直接存儲值外,其它類型都為指針,指向各自的結構。

類型

標量類型

最簡單的類型是true、false、long、double、null,其中true、false、null沒有value,直接根據type區(qū)分,而long、double的值則直接存在value中:zend_long、double,也就是標量類型不需要額外的value指針。

字符串

PHP中字符串通過zend_string表示:

struct _zend_string {
   zend_refcounted_h gc;
   zend_ulong        h;                /* hash value */
   size_t            len;
   char              val[1];
};

gc: 變量引用信息,比如當前value的引用數,所有用到引用計數的變量類型都會有這個結構,3.1節(jié)會詳細分析
h: 哈希值,數組中計算索引時會用到
len: 字符串長度,通過這個值保證二進制安全
val: 字符串內容,變長struct,分配時按len長度申請內存

數組

array是PHP中非常強大的一個數據結構,它的底層實現就是普通的有序HashTable,這里簡單看下它的結構。

typedef struct _zend_array HashTable;

struct _zend_array {
    zend_refcounted_h gc; //引用計數信息,與字符串相同
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    flags,
                zend_uchar    nApplyCount,
                zend_uchar    nIteratorsCount,
                zend_uchar    reserve)
        } v;
        uint32_t flags;
    } u;
    uint32_t          nTableMask; //計算bucket索引時的掩碼
    Bucket           *arData; //bucket數組
    uint32_t          nNumUsed; //已用bucket數
    uint32_t          nNumOfElements; //已有元素數,nNumOfElements <= nNumUsed,因為刪除的并不是直接從arData中移除
    uint32_t          nTableSize; //數組的大小,為2^n
    uint32_t          nInternalPointer; //數值索引
    zend_long         nNextFreeElement;
    dtor_func_t       pDestructor;
};
對象/資源
struct _zend_object {
    zend_refcounted_h gc;
    uint32_t          handle;
    zend_class_entry *ce; //對象對應的class類
    const zend_object_handlers *handlers;
    HashTable        *properties; //對象屬性哈希表
    zval              properties_table[1];
};

struct _zend_resource {
    zend_refcounted_h gc;
    int               handle;
    int               type;
    void             *ptr;
};
引用

在PHP中通過&操作符產生一個引用變量,也就是說不管以前的類型是什么,&首先會創(chuàng)建一個zend_reference結構,其內嵌了一個zval,這個zval的value指向原來zval的value(如果是布爾、整形、浮點則直接復制原來的值),然后將原zval的類型修改為IS_REFERENCE,原zval的value指向新創(chuàng)建的zend_reference結構。

struct _zend_reference {
    zend_refcounted_h gc;
    zval              val;
};

結構非常簡單,除了公共部分zend_refcounted_h外只有一個val,舉個示例看下具體的結構關系:

$a = "time:" . time();      //$a    -> zend_string_1(refcount=1)
$b = &$a;                   //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)

最終的結果如圖:


image.png

注意:引用只能通過&產生,無法通過賦值傳遞,比如:

$a = "time:" . time();      //$a    -> zend_string_1(refcount=1)
$b = &$a;                   //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = $b;                    //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=2)
                            //$c    ->                                 ---

b = &a這時候a、b的類型是引用,但是c = b并不會直接將b賦值給c,而是把b實際指向的zval賦值給c,如果想要$c也是一個引用則需要這么操作:

$a = "time:" . time();      //$a       -> zend_string_1(refcount=1)
$b = &$a;                   //$a,$b    -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = &$b;/*或$c = &$a*/     //$a,$b,$c -> zend_reference_1(refcount=3) -> zend_string_1(refcount=1) 

這個也表示PHP中的引用只可能有一層 ,不會出現一個引用指向另外一個引用的情況 。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容