摘要:第一部分講了和中關(guān)于變量最基礎(chǔ)的實(shí)現(xiàn)和變化。對象實(shí)際并不是直接嵌入到對象存儲的中的,因?yàn)閷ο蟛皇嵌ㄩL的?,F(xiàn)在看看對象存儲中指針指向的實(shí)際的的結(jié)構(gòu),通常情況下用戶層面的對象定義如下指針指向的是對象實(shí)現(xiàn)的類原型。
本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開發(fā)組成員,柏林科技大學(xué)的學(xué)生) 的博客。為了更符合漢語的閱讀習(xí)慣,文中并不會逐字逐句的翻譯。
要理解本文,你應(yīng)該對 PHP5 中變量的實(shí)現(xiàn)有了一些了解,本文重點(diǎn)在于解釋 PHP7 中 zval 的變化。
第一部分講了 PHP5 和 PHP7 中關(guān)于變量最基礎(chǔ)的實(shí)現(xiàn)和變化。這里再重復(fù)一下,主要的變化就是 zval 不再多帶帶分配內(nèi)存,不自己存儲引用計(jì)數(shù)。整型浮點(diǎn)型等簡單類型直接存儲在 zval 中。復(fù)雜類型則通過指針指向一個(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è)頭存儲有 refcount(引用計(jì)數(shù)),值的類型 type 和循環(huán)回收的相關(guān)信息 gc_info 以及類型標(biāo)志位 flags。
接下來會對每種復(fù)雜類型的實(shí)現(xiàn)多帶帶進(jìn)行分析并和 PHP5 的實(shí)現(xiàn)進(jìn)行比較。引用雖然也屬于復(fù)雜類型,但是上一部分已經(jīng)介紹過了,這里就不再贅述。另外這里也不會講到資源類型(因?yàn)樽髡哂X得資源類型沒什么好講的)。
字符串PHP7 中定義了一個(gè)新的結(jié)構(gòu)體 zend_string 用于存儲字符串變量:
struct _zend_string { zend_refcounted gc; zend_ulong h; /* hash value */ size_t len; char val[1]; };
除了引用計(jì)數(shù)的頭以外,字符串還包含哈希緩存 h,字符串長度 len 以及字符串的值 val。哈希緩存的存在是為了防止使用字符串做為 hashtable 的 key 在查找時(shí)需要重復(fù)計(jì)算其哈希值,所以這個(gè)在使用之前就對其進(jìn)行初始化。
如果你對 C 語言了解的不是很深入的話,可能會覺得 val 的定義有些奇怪:這個(gè)聲明只有一個(gè)元素,但是顯然我們想存儲的字符串償付肯定大于一個(gè)字符的長度。這里其實(shí)使用的是結(jié)構(gòu)體的一個(gè)『黑』方法:在聲明數(shù)組時(shí)只定義一個(gè)元素,但是實(shí)際創(chuàng)建 zend_string 時(shí)再分配足夠的內(nèi)存來存儲整個(gè)字符串。這樣我們還是可以通過 val 訪問完整的字符串。
當(dāng)然這屬于非常規(guī)的實(shí)現(xiàn)手段,因?yàn)槲覀儗?shí)際的讀和寫的內(nèi)容都超過了單字符數(shù)組的邊界。但是 C 語言編譯器卻不知道你是這么做的。雖然 C99 也曾明確規(guī)定過支持『柔性數(shù)組』,但是感謝我們的好朋友微軟,沒人能在不同的平臺上保證 C99 的一致性(所以這種手段是為了解決 Windows 平臺下柔性數(shù)組的支持問題)。
新的字符串類型的結(jié)構(gòu)比原生的 C 字符串更方便使用:第一是因?yàn)橹苯哟鎯α俗址拈L度,這樣就不用每次使用時(shí)都去計(jì)算。第二是字符串也有引用計(jì)數(shù)的頭,這樣也就可以在不同的地方共享字符串本身而無需使用 zval。一個(gè)經(jīng)常使用的地方就是共享 hashtable 的 key。
但是新的字符串類型也有一個(gè)很不好的地方:雖然可以很方便的從 zend_string 中取出 C 字符串(使用 str->val 即可),但反過來,如果將 C 字符串變成 zend_string 就需要先分配 zend_string 需要的內(nèi)存,再將字符串復(fù)制到 zend_string 中。這在實(shí)際使用的過程中并不是很方便。
字符串也有一些特有的標(biāo)志(存儲在 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),這樣它就可以一直存在而不是只在單次請求中有效。給這種特殊的分配打上標(biāo)記便于 zval 使用持久化字符串。在 PHP5 中并不是這樣處理的,是在使用前復(fù)制一份到 ZMM 中。
保留字符(interned strings)有點(diǎn)特殊,它會一直存在直到請求結(jié)束時(shí)才銷毀,所以也就無需進(jìn)行引用計(jì)數(shù)。保留字符串也不可重復(fù)(duplicate),所以在創(chuàng)建新的保留字符時(shí)也會先檢查是否有同樣字符的已經(jīng)存在。所有 PHP 源碼中不可變的字符串都是保留字符(包括字符串常量、變量名函數(shù)名等)。持久化字符串也是請求開始之前已經(jīng)創(chuàng)建好的保留字符。但普通的保留字符在請求結(jié)束后會銷毀,持久化字符串卻始終存在。
如果使用了 opcache 的話保留字符會被存儲在共享內(nèi)存(SHM)中這樣就可以在所有 PHP 進(jìn)程質(zhì)檢共享。這種情況下持久化字符串也就沒有存在的意義了,因?yàn)楸A糇址彩遣粫讳N毀的。
數(shù)組因?yàn)橹暗奈恼掠兄v過新的數(shù)組實(shí)現(xiàn),所以這里就不再詳細(xì)描述了。雖然最近有些變化導(dǎo)致之前的描述不是十分準(zhǔn)確了,但是基本的概念還是一致的。
這里要說的是之前的文章中沒有提到的數(shù)組相關(guān)的概念:不可變數(shù)組。其本質(zhì)上和保留字符類似:沒有引用計(jì)數(shù)且在請求結(jié)束之前一直存在(也可能在請求結(jié)束之后還存在)。
因?yàn)槟承﹥?nèi)存管理方便的原因,不可變數(shù)組只會在開啟 opcache 時(shí)會使用到。我們來看看實(shí)際使用的例子,先看以下的腳本:
開啟 opcache 時(shí),以上代碼會使用 32MB 的內(nèi)存,不開啟的情況下因?yàn)?$array 每個(gè)元素都會復(fù)制一份 ["foo"] ,所以需要 390MB。這里會進(jìn)行完整的復(fù)制而不是增加引用計(jì)數(shù)值的原因是防止 zend 虛擬機(jī)操作符執(zhí)行的時(shí)候出現(xiàn)共享內(nèi)存出錯(cuò)的情況。我希望不使用 opcache 時(shí)內(nèi)存暴增的問題以后能得到改善。
PHP5 中的對象在了解 PHP7 中的對象實(shí)現(xiàn)直線我們先看一下 PHP5 的并且看一下有什么效率上的問題。PHP5 中的 zval 會存儲一個(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 是對象的唯一 ID,可以用于查找對象數(shù)據(jù)。handles 是保存對象各種屬性方法的虛函數(shù)表指針。通常情況下 PHP 對象都有著同樣的 handler 表,但是 PHP 擴(kuò)展創(chuàng)建的對象也可以通過操作符重載等方式對其行為自定義。
對象句柄(handler)是作為索引用于『對象存儲』,對象存儲本身是一個(gè)存儲容器(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ù)(對象的析構(gòu)函數(shù)是否被調(diào)用過、bucke 是否被使用過以及對象被遞歸調(diào)用過多少次)。接下來的聯(lián)合體用于區(qū)分 bucket 是處于使用中的狀態(tài)還是空閑狀態(tài)。上面的結(jié)構(gòu)中最重要的是 struct _store_object 子結(jié)構(gòu)體:
第一個(gè)成員 object 是指向?qū)嶋H對象(也就是對象最終存儲的位置)的指針。對象實(shí)際并不是直接嵌入到對象存儲的 bucket 中的,因?yàn)閷ο蟛皇嵌ㄩL的。對象指針下面是三個(gè)用于管理對象銷毀、釋放與克隆的操作句柄(handler)。這里要注意的是 PHP 銷毀和釋放對象是不同的步驟,前者在某些情況下有可能會被跳過(不完全釋放)??寺〔僮鲗?shí)際上幾乎幾乎不會被用到,因?yàn)檫@里包含的操作不是普通對象本身的一部分,所以(任何時(shí)候)他們在每個(gè)對象中他們都會被多帶帶復(fù)制(duplicate)一份而不是共享。
這些對象存儲操作句柄后面是一個(gè)普通的對象 handlers 指針。存儲這幾個(gè)數(shù)據(jù)是因?yàn)橛袝r(shí)候可能會在 zval 未知的情況下銷毀對象(通常情況下這些操作都是針對 zval 進(jìn)行的)。
bucket 也包含了 refcount 的字段,不過這種行為在 PHP5 中顯得有些奇怪,因?yàn)?zval 本身已經(jīng)存儲了引用計(jì)數(shù)。為什么還需要一個(gè)多余的計(jì)數(shù)呢?問題在于雖然通常情況下 zval 的『復(fù)制』行為都是簡單的增加引用計(jì)數(shù)即可,但是偶爾也會有深度復(fù)制的情況出現(xiàn),比如創(chuàng)建一個(gè)全新的 zval 但是保存同樣的 zend_object_value。這種情況下兩個(gè)不同的 zval 就用到了同一個(gè)對象存儲的 bucket,所以 bucket 自身也需要進(jìn)行引用計(jì)數(shù)。這種『雙重計(jì)數(shù)』的方式是 PHP5 的實(shí)現(xiàn)內(nèi)在的問題。GC 根緩沖區(qū)中的 buffered 指針也是由于同樣的原因才需要進(jìn)行完全復(fù)制(duplicate)。
現(xiàn)在看看對象存儲中指針指向的實(shí)際的 object 的結(jié)構(gòu),通常情況下用戶層面的對象定義如下:
typedef struct _zend_object { zend_class_entry *ce; HashTable *properties; zval **properties_table; HashTable *guards; } zend_object;zend_class_entry 指針指向的是對象實(shí)現(xiàn)的類原型。接下來的兩個(gè)元素是使用不同的方式存儲對象屬性。動(dòng)態(tài)屬性(運(yùn)行時(shí)添加的而不是在類中定義的)全部存在 properties 中,不過只是屬性名和值的簡單匹配。
不過這里有針對已經(jīng)聲明的屬性的一個(gè)優(yōu)化:編譯期間每個(gè)屬性都會被指定一個(gè)索引并且屬性本身是存儲在 properties_table 的索引中。屬性名稱和索引的匹配存儲在類原型的 hashtable 中。這樣就可以防止每個(gè)對象使用的內(nèi)存超過 hashtable 的上限,并且屬性的索引會在運(yùn)行時(shí)有多處緩存。
guards 的哈希表是用于實(shí)現(xiàn)魔術(shù)方法的遞歸行為的,比如 __get,這里我們不深入討論。
除了上文提到過的雙重計(jì)數(shù)的問題,這種實(shí)現(xiàn)還有一個(gè)問題是一個(gè)最小的只有一個(gè)屬性的對象也需要 136 個(gè)字節(jié)的內(nèi)存(這還不算 zval 需要的內(nèi)存)。而且中間存在很多間接訪問動(dòng)作:比如要從對象 zval 中取出一個(gè)元素,先需要取出對象存儲 bucket,然后是 zend object,然后才能通過指針找到對象屬性表和 zval。這樣這里至少就有 4 層間接訪問(并且實(shí)際使用中可能最少需要七層)。
PHP7 中的對象PHP7 的實(shí)現(xiàn)中試圖解決上面這些問題,包括去掉雙重引用計(jì)數(shù)、減少內(nèi)存使用以及間接訪問。新的 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è)對象的全部內(nèi)容了:zend_object_value 已經(jīng)被替換成一個(gè)直接指向?qū)ο蠛蛯ο蟠鎯Φ闹羔?,雖然沒有完全移除,但已經(jīng)是很大的提升了。
除了 PHP7 中慣用的 zend_refcounted 頭以外,handle 和 對象的 handlers 現(xiàn)在也被放到了 zend_object 中。這里的 properties_table 同樣用到了 C 結(jié)構(gòu)體的小技巧,這樣 zend_object 和屬性表就會得到一整塊內(nèi)存。當(dāng)然,現(xiàn)在屬性表是直接嵌入到 zval 中的而不是指針。
現(xiàn)在對象結(jié)構(gòu)體中沒有了 guards 表,現(xiàn)在如果需要的話這個(gè)字段的值會被存儲在 properties_table 的第一位中,也就是使用 __get 等方法的時(shí)候。不過如果沒有使用魔術(shù)方法的話,guards 表會被省略。
dtor、free_storage? 和 ?clone 三個(gè)操作句柄之前是存儲在對象操作 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)部的對象總是嵌入到標(biāo)準(zhǔn)的 zend_object 中,但是也總會有添加一些成員進(jìn)去的需求。在 PHP5 中解決這個(gè)問題的方法是添加一些內(nèi)容到標(biāo)準(zhǔn)的對象后面:
struct custom_object { zend_object std; uint32_t something; // ... };這樣如果你可以輕易的將 zend_object* 添加到 struct custom_object* 中。這也是 C 語言中常用的結(jié)構(gòu)體繼承的做法。但是在 PHP7 中這種實(shí)現(xiàn)會有一個(gè)問題:因?yàn)?zend_object 在存儲屬性表時(shí)用了結(jié)構(gòu)體 hack 的技巧,zend_object 尾部存儲的 PHP 屬性會覆蓋掉后續(xù)添加進(jìn)去的內(nèi)部成員。所以 PHP7 的實(shí)現(xiàn)中會把自己添加的成員添加到標(biāo)準(zhǔn)對象結(jié)構(gòu)的前面:
struct custom_object { uint32_t something; // ... zend_object std; };不過這樣也就意味著現(xiàn)在無法直接在 zend_object* 和 struct custom_object* 進(jìn)行簡單的轉(zhuǎn)換了,因?yàn)閮烧叨家粋€(gè)偏移分割開了。所以這個(gè)偏移量就需要被存儲在對象 handler 表中的第一個(gè)元素中,這樣在編譯時(shí)通過 offsetof() 宏就能確定具體的偏移值。
也許你會好奇既然現(xiàn)在已經(jīng)直接(在 zend_value 中)存儲了 zend_object 的指針,那現(xiàn)在就不需要再到對象存儲中去查找對象了,為什么 PHP7 的對象者還保留著 handle 字段呢?
這是因?yàn)楝F(xiàn)在對象存儲仍然存在,雖然得到了極大的簡化,所以保留 handle 仍然是有必要的?,F(xiàn)在它只是一個(gè)指向?qū)ο蟮闹羔様?shù)組。當(dāng)對象被創(chuàng)建時(shí),會有一個(gè)指針插入到對象存儲中并且其索引會保存在 handle 中,當(dāng)對象被釋放時(shí),索引也會被移除。
那么為什么現(xiàn)在還需要對象存儲呢?因?yàn)樵谡埱蠼Y(jié)束的階段會在存在某個(gè)節(jié)點(diǎn),在這之后再去執(zhí)行用戶代碼并且取指針數(shù)據(jù)時(shí)就不安全了。為了避免這種情況出現(xiàn) PHP 會在更早的節(jié)點(diǎn)上執(zhí)行所有對象的析構(gòu)函數(shù)并且之后就不再有此類操作,所以就需要一個(gè)活躍對象的列表。
并且 handle 對于調(diào)試也是很有用的,它讓每個(gè)對象都有了一個(gè)唯一的 ID,這樣就很容易區(qū)分兩個(gè)對象是同一個(gè)還是只是有相同的內(nèi)容。雖然 HHVM 沒有對象存儲的概念,但它也存了對象的 handle。
和 PHP5 相比,現(xiàn)在的實(shí)現(xiàn)中只有一個(gè)引用計(jì)數(shù)(zval 自身不計(jì)數(shù)),并且內(nèi)存的使用量有了很大的縮減:40 個(gè)字節(jié)用于基礎(chǔ)對象,每個(gè)屬性需要 16 個(gè)字節(jié),并且這還是算了 zval 之后的。間接訪問的情況也有了顯著的改善,因?yàn)楝F(xiàn)在中間層的結(jié)構(gòu)體要么被去掉了,要么就是直接嵌入的,所以現(xiàn)在讀取一個(gè)屬性只有一層訪問而不再是四層。
間接 zval到現(xiàn)在我們已經(jīng)基本提到過了所有正常的 zval 類型,但是也有一對特殊類型用于某些特定的情況的,其中之一就是 PHP7 新添加的 IS_INDIRECT。
間接 zval 指的就是其真正的值是存儲在其他地方的。注意這個(gè) IS_REFERENCE 類型是不同的,間接 zval 是直接指向另外一個(gè) zval 而不是像 zend_reference 結(jié)構(gòu)體一樣嵌入 zval。
為了理解在什么時(shí)候會出現(xiàn)這種情況,我們來看一下 PHP 中變量的實(shí)現(xiàn)(實(shí)際上對象屬性的存儲也是一樣的情況)。
所有在編譯過程中已知的變量都會被指定一個(gè)索引并且其值會被存在編譯變量(CV)表的相應(yīng)位置中。但是 PHP 也允許你動(dòng)態(tài)的引用變量,不管是局部變量還是全局變量(比如 $GLOBALS),只要出現(xiàn)這種情況,PHP 就會為腳本或者函數(shù)創(chuàng)建一個(gè)符號表,這其中包含了變量名和它們的值之間的映射關(guān)系。
但是問題在于:怎么樣才能實(shí)現(xiàn)兩個(gè)表的同時(shí)訪問呢?我們需要在 CV 表中能夠訪問普通變量,也需要能在符號表中訪問編譯變量。在 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)需要使用符號表時(shí)存儲 zval* 的中間表其實(shí)是沒有用到的而 zval** 指針會被更新到 hashtable buckets 的響應(yīng)位置中。我們假定有 $a、$b 和 $c 三個(gè)變量,下面是簡單的示意圖:
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)沒有這個(gè)問題了,因?yàn)?PHP7 中的 hashtable 大小發(fā)生變化時(shí) hashtable bucket?就失效了。所以 PHP7 用了一個(gè)相反的策略:為了訪問 CV 表中存儲的變量,符號表中存儲 INDIRECT 來指向 CV 表。CV 表在符號表的生命周期內(nèi)不會重新分配,所以也就不會存在有無效指針的問題了。
所以加入你有一個(gè)函數(shù)并且在 CV 表中有 $a、$b 和 $c,同時(shí)還有一個(gè)動(dòng)態(tài)分配的變量 $d,符號表的結(jié)構(gòu)看起來大概就是這個(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 沒有和它關(guān)聯(lián)的 key 時(shí)就會出現(xiàn)這種情況。所以當(dāng)使用 unset($a) 將 CV[0] 的類型標(biāo)記為 UNDEF 時(shí)就會判定符號表不存在鍵值為 a 的數(shù)據(jù)。
常量和 AST還有兩個(gè)需要說一下的在 PHP5 和 PHP7 中都存在的特殊類型 IS_CONSTANT 和 IS_CONSTANT_AST。要了解他們我們還是先看以下的例子:
test() 函數(shù)的兩個(gè)參數(shù)的默認(rèn)值都是由常量 ANSWER構(gòu)成,但是函數(shù)聲明時(shí)常量的值尚未定義。常量的具體值只有通過 define() 定義時(shí)才知道。
由于以上問題的存在,參數(shù)和屬性的默認(rèn)值、常量以及其他接受『靜態(tài)表達(dá)式』的東西都支持『延時(shí)綁定』直到首次使用時(shí)。
常量(或者類的靜態(tài)屬性)這些需要『延時(shí)綁定』的數(shù)據(jù)就是最常需要用到 IS_CONSTANT 類型 zval 的地方。如果這個(gè)值是表達(dá)式,就會使用 IS_CONSTANT_AST 類型的 zval 指向表達(dá)式的抽象語法樹(AST)。
到這里我們就結(jié)束了對 PHP7 中變量實(shí)現(xiàn)的分析。后面我可能還會寫兩篇文章來介紹一些虛擬機(jī)優(yōu)化、新的命名約定以及一些編譯器基礎(chǔ)結(jié)構(gòu)的優(yōu)化的內(nèi)容(這是作者原話)。
譯者注:兩篇文章篇幅較長,翻譯中可能有疏漏或不正確的地方,如果發(fā)現(xiàn)了請及時(shí)指正。
私博地址:http://0x1.im
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/21283.html
摘要:注意這里提及到的引用計(jì)數(shù)指的不是代碼中的引用使用,而是變量的使用次數(shù)。之前當(dāng)在一個(gè)新的地方使用時(shí)會復(fù)制一份并增加一次引用計(jì)數(shù)。屬于深度復(fù)制,比如在復(fù)制數(shù)組時(shí),不僅僅是簡單增加數(shù)組的引用計(jì)數(shù),而是制造一份全新值一樣的數(shù)組。 本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開發(fā)組成員,柏林科技大學(xué)的學(xué)生) 的博客。為了更符合漢語的閱讀習(xí)慣,文中并不會逐字逐句的翻...
摘要:安裝好,開始按照流程編譯拓展,新的問題出現(xiàn)了。參考的方案將其值改成。繼續(xù)編譯,變量初始化錯(cuò)誤導(dǎo)致類似問題。所以很有可能是因?yàn)樽兞课丛O(shè)置正確導(dǎo)致的。 轉(zhuǎn)載請注明文章出處:https://tlanyan.me/solve-buil... 接上篇Windows編譯PHP7.2拓展,以為編譯PHP7.1的拓展應(yīng)該水到渠成,馬到成功。哪知道編譯PHP7.1拓展出現(xiàn)了新問題,折騰更超7.2。 第...
摘要:下載源碼并解壓進(jìn)入正題,要編譯安裝首先當(dāng)然要下載的源碼。啟動(dòng)服務(wù)通過查看是否啟動(dòng)成功至此,就安裝成功了,你也開始使用吧 首先推薦一篇文章PHP 7 Release Date Arrived: Will Developers Adopt PHP 7? - PHP Classes blog。 里面說到是否會去使用PHP7,就個(gè)人而言,我是毫不猶豫地使用的,但是生產(chǎn)環(huán)境就不是我說了算,所以只...
閱讀 2816·2021-11-24 09:39
閱讀 1671·2021-09-28 09:35
閱讀 1148·2021-09-06 15:02
閱讀 1364·2021-07-25 21:37
閱讀 2797·2019-08-30 15:53
閱讀 3675·2019-08-30 14:07
閱讀 735·2019-08-30 11:07
閱讀 3553·2019-08-29 18:36