溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

[譯]變量在 PHP7 內(nèi)部的實(shí)現(xiàn)(二)

發(fā)布時(shí)間:2020-08-19 09:29:29 來(lái)源:網(wǎng)絡(luò) 閱讀:515 作者:wz669 欄目:web開(kāi)發(fā)

→ About → Links → Github → 公眾號(hào)[譯]變量在 PHP7 內(nèi)部的實(shí)現(xiàn)(二)

Scholer's Blog

[譯]變量在 PHP7 內(nèi)部的實(shí)現(xiàn)二

Dec 21, 2015

本文第一部分和第二均翻譯自Nikita Popov(nikicPHP 官方開(kāi)發(fā)組成員柏林科技大學(xué)的學(xué)生) 的博客。為了更符合漢語(yǔ)的閱讀習(xí)慣文中并不會(huì)逐字逐句的翻譯。

要理解本文你應(yīng)該對(duì) PHP5 中變量的實(shí)現(xiàn)有了一些了解本文重點(diǎn)在于解釋 PHP7 中 zval 的變化。

第一部分講了 PHP5 和 PHP7 中關(guān)于變量最基礎(chǔ)的實(shí)現(xiàn)和變化。這里再重復(fù)一下主要的變化就是 zval 不再單獨(dú)分配內(nèi)存不自己存儲(chǔ)引用計(jì)數(shù)。整型浮點(diǎn)型等簡(jiǎn)單類型直接存儲(chǔ)在 zval 中。復(fù)雜類型則通過(guò)指針指向一個(gè)獨(dú)立的結(jié)構(gòu)體。

復(fù)雜的 zval 數(shù)據(jù)值有一個(gè)共同的頭其結(jié)構(gòu)由 zend_refcounted 定義

struct _zend_refcounted {
    uint32_t refcount;
    union {
        struct {
            ZEND_ENDIAN_LOHI_3(
                zend_uchar    type,
                zend_uchar    flags,
                uint16_t      gc_info)
        } v;
        uint32_t type_info;
    } u;};

這個(gè)頭存儲(chǔ)有 refcount引用計(jì)數(shù)值的類型 type 和循環(huán)回收的相關(guān)信息 gc_info 以及類型標(biāo)志位flags。

接下來(lái)會(huì)對(duì)每種復(fù)雜類型的實(shí)現(xiàn)單獨(dú)進(jìn)行分析并和 PHP5 的實(shí)現(xiàn)進(jìn)行比較。引用雖然也屬于復(fù)雜類型但是上一部分已經(jīng)介紹過(guò)了這里就不再贅述。另外這里也不會(huì)講到資源類型因?yàn)樽髡哂X(jué)得資源類型沒(méi)什么好講的。

字符串

PHP7 中定義了一個(gè)新的結(jié)構(gòu)體 zend_string 用于存儲(chǔ)字符串變量

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

除了引用計(jì)數(shù)的頭以外字符串還包含哈希緩存 h字符串長(zhǎng)度 len 以及字符串的值 val。哈希緩存的存在是為了防止使用字符串做為 hashtable 的 key 在查找時(shí)需要重復(fù)計(jì)算其哈希值所以這個(gè)在使用之前就對(duì)其進(jìn)行初始化。

如果你對(duì) C 語(yǔ)言了解的不是很深入的話可能會(huì)覺(jué)得 val 的定義有些奇怪這個(gè)聲明只有一個(gè)元素但是顯然我們想存儲(chǔ)的字符串長(zhǎng)度肯定大于一個(gè)字符的長(zhǎng)度。這里其實(shí)使用的是結(jié)構(gòu)體的一個(gè)『黑』方法在聲明數(shù)組時(shí)只定義一個(gè)元素但是實(shí)際創(chuàng)建 zend_string 時(shí)再分配足夠的內(nèi)存來(lái)存儲(chǔ)整個(gè)字符串。這樣我們還是可以通過(guò) val 訪問(wèn)完整的字符串。

當(dāng)然這屬于非常規(guī)的實(shí)現(xiàn)手段因?yàn)槲覀儗?shí)際的讀和寫的內(nèi)容都超過(guò)了單字符數(shù)組的邊界。但是 C 語(yǔ)言編譯器卻不知道你是這么做的。雖然 C99 也曾明確規(guī)定過(guò)支持『柔性數(shù)組』但是感謝我們的好朋友微軟沒(méi)人能在不同的平臺(tái)上保證 C99 的一致性所以這種手段是為了解決 Windows 平臺(tái)下柔性數(shù)組的支持問(wèn)題。

新的字符串類型的結(jié)構(gòu)比原生的 C 字符串更方便使用第一是因?yàn)橹苯哟鎯?chǔ)了字符串的長(zhǎng)度這樣就不用每次使用時(shí)都去計(jì)算。第二是字符串也有引用計(jì)數(shù)的頭這樣也就可以在不同的地方共享字符串本身而無(wú)需使用 zval。一個(gè)經(jīng)常使用的地方就是共享 hashtable 的 key。

但是新的字符串類型也有一個(gè)很不好的地方雖然可以很方便的從 zend_string 中取出 C 字符串使用 str->val 即可但反過(guò)來(lái)如果將 C 字符串變成 zend_string 就需要先分配 zend_string 需要的內(nèi)存再將字符串復(fù)制到 zend_string 中。這在實(shí)際使用的過(guò)程中并不是很方便。

字符串也有一些特有的標(biāo)志存儲(chǔ)在 GC 的標(biāo)志位中的

#define IS_STR_PERSISTENT           (1<<0) /* allocated using malloc */#define IS_STR_INTERNED             (1<<1) /* interned string */#define IS_STR_PERMANENT            (1<<2) /* interned string surviving request boundary */

持久化的字符串需要的內(nèi)存直接從系統(tǒng)本身分配而不是 zend 內(nèi)存管理器ZMM這樣它就可以一直存在而不是只在單次請(qǐng)求中有效。給這種特殊的分配打上標(biāo)記便于 zval 使用持久化字符串。在 PHP5 中并不是這樣處理的是在使用前復(fù)制一份到 ZMM 中。

保留字符interned strings有點(diǎn)特殊它會(huì)一直存在直到請(qǐng)求結(jié)束時(shí)才銷毀所以也就無(wú)需進(jìn)行引用計(jì)數(shù)。保留字符串也不可重復(fù)duplicate所以在創(chuàng)建新的保留字符時(shí)也會(huì)先檢查是否有同樣字符的已經(jīng)存在。所有 PHP 源碼中不可變的字符串都是保留字符包括字符串常量、變量名函數(shù)名等。持久化字符串也是請(qǐng)求開(kāi)始之前已經(jīng)創(chuàng)建好的保留字符。但普通的保留字符在請(qǐng)求結(jié)束后會(huì)銷毀持久化字符串卻始終存在。

如果使用了 opcache 的話保留字符會(huì)被存儲(chǔ)在共享內(nèi)存SHM中這樣就可以在所有 PHP 進(jìn)程質(zhì)檢共享。這種情況下持久化字符串也就沒(méi)有存在的意義了因?yàn)楸A糇址彩遣粫?huì)被銷毀的。

數(shù)組

因?yàn)橹暗奈恼掠兄v過(guò)新的數(shù)組實(shí)現(xiàn)所以這里就不再詳細(xì)描述了。雖然最近有些變化導(dǎo)致之前的描述不是十分準(zhǔn)確了但是基本的概念還是一致的。

這里要說(shuō)的是之前的文章中沒(méi)有提到的數(shù)組相關(guān)的概念不可變數(shù)組。其本質(zhì)上和保留字符類似沒(méi)有引用計(jì)數(shù)且在請(qǐng)求結(jié)束之前一直存在也可能在請(qǐng)求結(jié)束之后還存在。

因?yàn)槟承﹥?nèi)存管理方便的原因不可變數(shù)組只會(huì)在開(kāi)啟 opcache 時(shí)會(huì)使用到。我們來(lái)看看實(shí)際使用的例子先看以下的腳本

<?phpfor ($i = 0; $i < 1000000; ++$i) {
    $array[] = ['foo'];}var_dump(memory_get_usage());

開(kāi)啟 opcache 時(shí)以上代碼會(huì)使用 32MB 的內(nèi)存不開(kāi)啟的情況下因?yàn)?nbsp;$array 每個(gè)元素都會(huì)復(fù)制一份['foo'] 所以需要 390MB。這里會(huì)進(jìn)行完整的復(fù)制而不是增加引用計(jì)數(shù)值的原因是防止 zend 虛擬機(jī)操作符執(zhí)行的時(shí)候出現(xiàn)共享內(nèi)存出錯(cuò)的情況。我希望不使用 opcache 時(shí)內(nèi)存暴增的問(wèn)題以后能得到改善。

PHP5 中的對(duì)象

在了解 PHP7 中的對(duì)象實(shí)現(xiàn)直線我們先看一下 PHP5 的并且看一下有什么效率上的問(wèn)題。PHP5 中的 zval 會(huì)存儲(chǔ)一個(gè) zend_object_value 結(jié)構(gòu)其定義如下

typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;} zend_object_value;

handle 是對(duì)象的唯一 ID可以用于查找對(duì)象數(shù)據(jù)。handles 是保存對(duì)象各種屬性方法的虛函數(shù)表指針。通常情況下 PHP 對(duì)象都有著同樣的 handler 表但是 PHP 擴(kuò)展創(chuàng)建的對(duì)象也可以通過(guò)操作符重載等方式對(duì)其行為自定義。

對(duì)象句柄handler是作為索引用于『對(duì)象存儲(chǔ)』對(duì)象存儲(chǔ)本身是一個(gè)存儲(chǔ)容器bucket的數(shù)組bucket 定義如下

typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    zend_uchar apply_count;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;} zend_object_store_bucket;

這個(gè)結(jié)構(gòu)體包含了很多東西。前三個(gè)成員只是些普通的元數(shù)據(jù)對(duì)象的析構(gòu)函數(shù)是否被調(diào)用過(guò)、bucke 是否被使用過(guò)以及對(duì)象被遞歸調(diào)用過(guò)多少次。接下來(lái)的聯(lián)合體用于區(qū)分 bucket 是處于使用中的狀態(tài)還是空閑狀態(tài)。上面的結(jié)構(gòu)中最重要的是 struct _store_object 子結(jié)構(gòu)體

第一個(gè)成員 object 是指向?qū)嶋H對(duì)象也就是對(duì)象最終存儲(chǔ)的位置的指針。對(duì)象實(shí)際并不是直接嵌入到對(duì)象存儲(chǔ)的 bucket 中的因?yàn)閷?duì)象不是定長(zhǎng)的。對(duì)象指針下面是三個(gè)用于管理對(duì)象銷毀、釋放與克隆的操作句柄handler。這里要注意的是 PHP 銷毀和釋放對(duì)象是不同的步驟前者在某些情況下有可能會(huì)被跳過(guò)不完全釋放??寺〔僮鲗?shí)際上幾乎幾乎不會(huì)被用到因?yàn)檫@里包含的操作不是普通對(duì)象本身的一部分所以任何時(shí)候他們?cè)诿總€(gè)對(duì)象中他們都會(huì)被單獨(dú)復(fù)制duplicate一份而不是共享。

這些對(duì)象存儲(chǔ)操作句柄后面是一個(gè)普通的對(duì)象 handlers 指針。存儲(chǔ)這幾個(gè)數(shù)據(jù)是因?yàn)橛袝r(shí)候可能會(huì)在 zval 未知的情況下銷毀對(duì)象通常情況下這些操作都是針對(duì) zval 進(jìn)行的。

bucket 也包含了 refcount 的字段不過(guò)這種行為在 PHP5 中顯得有些奇怪因?yàn)?zval 本身已經(jīng)存儲(chǔ)了引用計(jì)數(shù)。為什么還需要一個(gè)多余的計(jì)數(shù)呢問(wèn)題在于雖然通常情況下 zval 的『復(fù)制』行為都是簡(jiǎn)單的增加引用計(jì)數(shù)即可但是偶爾也會(huì)有深度復(fù)制的情況出現(xiàn)比如創(chuàng)建一個(gè)全新的 zval 但是保存同樣的zend_object_value。這種情況下兩個(gè)不同的 zval 就用到了同一個(gè)對(duì)象存儲(chǔ)的 bucket所以 bucket 自身也需要進(jìn)行引用計(jì)數(shù)。這種『雙重計(jì)數(shù)』的方式是 PHP5 的實(shí)現(xiàn)內(nèi)在的問(wèn)題。GC 根緩沖區(qū)中的 buffered 指針也是由于同樣的原因才需要進(jìn)行完全復(fù)制duplicate。

現(xiàn)在看看對(duì)象存儲(chǔ)中指針指向的實(shí)際的 object 的結(jié)構(gòu)通常情況下用戶層面的對(duì)象定義如下

typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards;} zend_object;

zend_class_entry 指針指向的是對(duì)象實(shí)現(xiàn)的類原型。接下來(lái)的兩個(gè)元素是使用不同的方式存儲(chǔ)對(duì)象屬性。動(dòng)態(tài)屬性運(yùn)行時(shí)添加的而不是在類中定義的全部存在 properties 中不過(guò)只是屬性名和值的簡(jiǎn)單匹配。

不過(guò)這里有針對(duì)已經(jīng)聲明的屬性的一個(gè)優(yōu)化編譯期間每個(gè)屬性都會(huì)被指定一個(gè)索引并且屬性本身是存儲(chǔ)在properties_table 的索引中。屬性名稱和索引的匹配存儲(chǔ)在類原型的 hashtable 中。這樣就可以防止每個(gè)對(duì)象使用的內(nèi)存超過(guò) hashtable 的上限并且屬性的索引會(huì)在運(yùn)行時(shí)有多處緩存。

guards 的哈希表是用于實(shí)現(xiàn)魔術(shù)方法的遞歸行為的比如 __get這里我們不深入討論。

除了上文提到過(guò)的雙重計(jì)數(shù)的問(wèn)題這種實(shí)現(xiàn)還有一個(gè)問(wèn)題是一個(gè)最小的只有一個(gè)屬性的對(duì)象也需要 136 個(gè)字節(jié)的內(nèi)存這還不算 zval 需要的內(nèi)存。而且中間存在很多間接訪問(wèn)動(dòng)作比如要從對(duì)象 zval 中取出一個(gè)元素先需要取出對(duì)象存儲(chǔ) bucket然后是 zend object然后才能通過(guò)指針找到對(duì)象屬性表和 zval。這樣這里至少就有 4 層間接訪問(wèn)并且實(shí)際使用中可能最少需要七層。

PHP7 中的對(duì)象

PHP7 的實(shí)現(xiàn)中試圖解決上面這些問(wèn)題包括去掉雙重引用計(jì)數(shù)、減少內(nèi)存使用以及間接訪問(wèn)。新的zend_object 結(jié)構(gòu)體如下

struct _zend_object {
    zend_refcounted   gc;
    uint32_t          handle;
    zend_class_entry *ce;
    const zend_object_handlers *handlers;
    HashTable        *properties;
    zval              properties_table[1];};

可以看到現(xiàn)在這個(gè)結(jié)構(gòu)體幾乎就是一個(gè)對(duì)象的全部?jī)?nèi)容了zend_object_value 已經(jīng)被替換成一個(gè)直接指向?qū)ο蠛蛯?duì)象存儲(chǔ)的指針雖然沒(méi)有完全移除但已經(jīng)是很大的提升了。

除了 PHP7 中慣用的 zend_refcounted 頭以外handle 和 對(duì)象的 handlers 現(xiàn)在也被放到了 zend_object中。這里的 properties_table 同樣用到了 C 結(jié)構(gòu)體的小技巧這樣 zend_object 和屬性表就會(huì)得到一整塊內(nèi)存。當(dāng)然現(xiàn)在屬性表是直接嵌入到 zval 中的而不是指針。

現(xiàn)在對(duì)象結(jié)構(gòu)體中沒(méi)有了 guards 表現(xiàn)在如果需要的話這個(gè)字段的值會(huì)被存儲(chǔ)在 properties_table 的第一位中也就是使用 __get 等方法的時(shí)候。不過(guò)如果沒(méi)有使用魔術(shù)方法的話guards 表會(huì)被省略。

dtor、free_storage  和  clone 三個(gè)操作句柄之前是存儲(chǔ)在對(duì)象操作 bucket 中現(xiàn)在直接存在 handlers 表中其結(jié)構(gòu)體定義如下

struct _zend_object_handlers {
    /* offset of real object header (usually zero) */
    int                                     offset;
    /* general object functions */
    zend_object_free_obj_t                  free_obj;
    zend_object_dtor_obj_t                  dtor_obj;
    zend_object_clone_obj_t                 clone_obj;
    /* individual object functions */
    // ... rest is about the same in PHP 5};

handler 表的第一個(gè)成員是 offset很顯然這不是一個(gè)操作句柄。這個(gè) offset 是現(xiàn)在的實(shí)現(xiàn)中必須存在的因?yàn)殡m然內(nèi)部的對(duì)象總是嵌入到標(biāo)準(zhǔn)的 zend_object 中但是也總會(huì)有添加一些成員進(jìn)去的需求。在 PHP5 中解決這個(gè)問(wèn)題的方法是添加一些內(nèi)容到標(biāo)準(zhǔn)的對(duì)象后面

struct custom_object {
    zend_object std;
    uint32_t something;
    // ...};

這樣如果你可以輕易的將 zend_object* 添加到 struct custom_object* 中。這也是 C 語(yǔ)言中常用的結(jié)構(gòu)體繼承的做法。但是在 PHP7 中這種實(shí)現(xiàn)會(huì)有一個(gè)問(wèn)題因?yàn)?nbsp;zend_object 在存儲(chǔ)屬性表時(shí)用了結(jié)構(gòu)體 hack 的技巧zend_object 尾部存儲(chǔ)的 PHP 屬性會(huì)覆蓋掉后續(xù)添加進(jìn)去的內(nèi)部成員。所以 PHP7 的實(shí)現(xiàn)中會(huì)把自己添加的成員添加到標(biāo)準(zhǔn)對(duì)象結(jié)構(gòu)的前面

struct custom_object {
    uint32_t something;
    // ...    zend_object std;};

不過(guò)這樣也就意味著現(xiàn)在無(wú)法直接在 zend_object* 和 struct custom_object* 進(jìn)行簡(jiǎn)單的轉(zhuǎn)換了因?yàn)閮烧叨家粋€(gè)偏移分割開(kāi)了。所以這個(gè)偏移量就需要被存儲(chǔ)在對(duì)象 handler 表中的第一個(gè)元素中這樣在編譯時(shí)通過(guò) offsetof() 宏就能確定具體的偏移值。

也許你會(huì)好奇既然現(xiàn)在已經(jīng)直接在 zend_value 中存儲(chǔ)了 zend_object 的指針那現(xiàn)在就不需要再到對(duì)象存儲(chǔ)中去查找對(duì)象了為什么 PHP7 的對(duì)象者還保留著 handle 字段呢

這是因?yàn)楝F(xiàn)在對(duì)象存儲(chǔ)仍然存在雖然得到了極大的簡(jiǎn)化所以保留 handle 仍然是有必要的?,F(xiàn)在它只是一個(gè)指向?qū)ο蟮闹羔様?shù)組。當(dāng)對(duì)象被創(chuàng)建時(shí)會(huì)有一個(gè)指針插入到對(duì)象存儲(chǔ)中并且其索引會(huì)保存在 handle 中當(dāng)對(duì)象被釋放時(shí)索引也會(huì)被移除。

那么為什么現(xiàn)在還需要對(duì)象存儲(chǔ)呢因?yàn)樵谡?qǐng)求結(jié)束的階段會(huì)在存在某個(gè)節(jié)點(diǎn)在這之后再去執(zhí)行用戶代碼并且取指針數(shù)據(jù)時(shí)就不安全了。為了避免這種情況出現(xiàn) PHP 會(huì)在更早的節(jié)點(diǎn)上執(zhí)行所有對(duì)象的析構(gòu)函數(shù)并且之后就不再有此類操作所以就需要一個(gè)活躍對(duì)象的列表。

并且 handle 對(duì)于調(diào)試也是很有用的它讓每個(gè)對(duì)象都有了一個(gè)唯一的 ID這樣就很容易區(qū)分兩個(gè)對(duì)象是同一個(gè)還是只是有相同的內(nèi)容。雖然 HHVM 沒(méi)有對(duì)象存儲(chǔ)的概念但它也存了對(duì)象的 handle。

和 PHP5 相比現(xiàn)在的實(shí)現(xiàn)中只有一個(gè)引用計(jì)數(shù)zval 自身不計(jì)數(shù)并且內(nèi)存的使用量有了很大的縮減40 個(gè)字節(jié)用于基礎(chǔ)對(duì)象每個(gè)屬性需要 16 個(gè)字節(jié)并且這還是算了 zval 之后的。間接訪問(wèn)的情況也有了顯著的改善因?yàn)楝F(xiàn)在中間層的結(jié)構(gòu)體要么被去掉了要么就是直接嵌入的所以現(xiàn)在讀取一個(gè)屬性只有一層訪問(wèn)而不再是四層。

間接 zval

到現(xiàn)在我們已經(jīng)基本提到過(guò)了所有正常的 zval 類型但是也有一對(duì)特殊類型用于某些特定的情況的其中之一就是 PHP7 新添加的 IS_INDIRECT

間接 zval 指的就是其真正的值是存儲(chǔ)在其他地方的。注意這個(gè) IS_REFERENCE 類型是不同的間接 zval 是直接指向另外一個(gè) zval 而不是像 zend_reference 結(jié)構(gòu)體一樣嵌入 zval。

為了理解在什么時(shí)候會(huì)出現(xiàn)這種情況我們來(lái)看一下 PHP 中變量的實(shí)現(xiàn)實(shí)際上對(duì)象屬性的存儲(chǔ)也是一樣的情況。

所有在編譯過(guò)程中已知的變量都會(huì)被指定一個(gè)索引并且其值會(huì)被存在編譯變量CV表的相應(yīng)位置中。但是 PHP 也允許你動(dòng)態(tài)的引用變量不管是局部變量還是全局變量比如 $GLOBALS只要出現(xiàn)這種情況PHP 就會(huì)為腳本或者函數(shù)創(chuàng)建一個(gè)符號(hào)表這其中包含了變量名和它們的值之間的映射關(guān)系。

但是問(wèn)題在于怎么樣才能實(shí)現(xiàn)兩個(gè)表的同時(shí)訪問(wèn)呢我們需要在 CV 表中能夠訪問(wèn)普通變量也需要能在符號(hào)表中訪問(wèn)編譯變量。在 PHP5 中 CV 表用了雙重指針 zval**通常這些指針指向中間的 zval* 的表zval* 最終指向的才是實(shí)際的 zval:

+------ CV_ptr_ptr[0]
| +---- CV_ptr_ptr[1]
| | +-- CV_ptr_ptr[2]
| | |
| | +-> CV_ptr[0] --> some zval
| +---> CV_ptr[1] --> some zval
+-----> CV_ptr[2] --> some zval

當(dāng)需要使用符號(hào)表時(shí)存儲(chǔ) zval* 的中間表其實(shí)是沒(méi)有用到的而 zval** 指針會(huì)被更新到 hashtable buckets 的響應(yīng)位置中。我們假定有 $a$b 和 $c 三個(gè)變量下面是簡(jiǎn)單的示意圖

CV_ptr_ptr[0] --> SymbolTable["a"].pDataPtr --> some zval
CV_ptr_ptr[1] --> SymbolTable["b"].pDataPtr --> some zval
CV_ptr_ptr[2] --> SymbolTable["c"].pDataPtr --> some zval

但是 PHP7 的用法中已經(jīng)沒(méi)有這個(gè)問(wèn)題了因?yàn)?PHP7 中的 hashtable 大小發(fā)生變化時(shí) hashtable bucket 就失效了。所以 PHP7 用了一個(gè)相反的策略為了訪問(wèn) CV 表中存儲(chǔ)的變量符號(hào)表中存儲(chǔ) INDIRECT 來(lái)指向 CV 表。CV 表在符號(hào)表的生命周期內(nèi)不會(huì)重新分配所以也就不會(huì)存在有無(wú)效指針的問(wèn)題了。

所以加入你有一個(gè)函數(shù)并且在 CV 表中有 $a、$b 和 $c同時(shí)還有一個(gè)動(dòng)態(tài)分配的變量 $d符號(hào)表的結(jié)構(gòu)看起來(lái)大概就是這個(gè)樣子

SymbolTable["a"].value = INDIRECT --> CV[0] = LONG 42
SymbolTable["b"].value = INDIRECT --> CV[1] = DOUBLE 42.0
SymbolTable["c"].value = INDIRECT --> CV[2] = STRING --> zend_string("42")
SymbolTable["d"].value = ARRAY --> zend_array([4, 2])

間接 zval 也可以是一個(gè)指向 IS_UNDEF 類型 zval 的指針當(dāng) hashtable 沒(méi)有和它關(guān)聯(lián)的 key 時(shí)就會(huì)出現(xiàn)這種情況。所以當(dāng)使用 unset($a) 將 CV[0] 的類型標(biāo)記為 UNDEF 時(shí)就會(huì)判定符號(hào)表不存在鍵值為 a 的數(shù)據(jù)。

常量和 AST

還有兩個(gè)需要說(shuō)一下的在 PHP5 和 PHP7 中都存在的特殊類型 IS_CONSTANT 和 IS_CONSTANT_AST。要了解他們我們還是先看以下的例子

<?phpfunction test($a = ANSWER,
              $b = ANSWER * ANSWER) {
    return $a + $b;}define('ANSWER', 42);var_dump(test()); // int(42 + 42 * 42)·

test() 函數(shù)的兩個(gè)參數(shù)的默認(rèn)值都是由常量 ANSWER構(gòu)成但是函數(shù)聲明時(shí)常量的值尚未定義。常量的具體值只有通過(guò) define() 定義時(shí)才知道。

由于以上問(wèn)題的存在參數(shù)和屬性的默認(rèn)值、常量以及其他接受『靜態(tài)表達(dá)式』的東西都支持『延時(shí)綁定』直到首次使用時(shí)。

常量或者類的靜態(tài)屬性這些需要『延時(shí)綁定』的數(shù)據(jù)就是最常需要用到 IS_CONSTANT 類型 zval 的地方。如果這個(gè)值是表達(dá)式就會(huì)使用 IS_CONSTANT_AST 類型的 zval 指向表達(dá)式的抽象語(yǔ)法樹(shù)AST。

到這里我們就結(jié)束了對(duì) PHP7 中變量實(shí)現(xiàn)的分析。后面我可能還會(huì)寫兩篇文章來(lái)介紹一些虛擬機(jī)優(yōu)化、新的命名約定以及一些編譯器基礎(chǔ)結(jié)構(gòu)的優(yōu)化的內(nèi)容這是作者原話。

譯者注兩篇文章篇幅較長(zhǎng)翻譯中可能有疏漏或不正確的地方如果發(fā)現(xiàn)了請(qǐng)及時(shí)指正。

More:

  • Jan 07,2017  再見(jiàn)2016我在騰訊這一年

  • Jan 02,2017  如何學(xué)習(xí) PHP 源碼 - 從編譯開(kāi)始

Website powered by Jekyll, hosted on Github and theme of marcgg 
All of the blog's articles are under Creative commons license unless stated otherwise. Everything else is .


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI