《PHP學習:變量在 PHP7 內部的實現(一)》要點:
本文介紹了PHP學習:變量在 PHP7 內部的實現(一),希望對您有用。如果有疑問,可以聯系我們。
<?php $array = range(0, 1000000); $ref =& $array; var_dump(count($array)); // <-- 這里會進行分離
由于大量的細節描述,本文將會分成兩個部分:第一部分主要描述 zval(zend value) 的實現在 PHP5 和 PHP7 中有何分歧以及引用的實現.第二部分將會分析單獨類型(strings、objects)的細節.PHP應用
PHP5 中的 zvalPHP應用
PHP5 中 zval 布局體定義如下:PHP應用
typedef struct _zval_struct { zvalue_value value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc; } zval;
如上,zval 包括一個 value、一個 type 以及兩個 __gc 后綴的字段.value 是個聯合體,用于存儲不同類型的值:PHP應用
typedef union _zvalue_value { long lval; // 用于 bool 類型、整型和資源類型 double dval; // 用于浮點類型 struct { // 用于字符串 char *val; int len; } str; HashTable *ht; // 用于數組 zend_object_value obj; // 用于對象 zend_ast *ast; // 用于常量表達式(PHP5.6 才有) } zvalue_value;
C 語言聯合體的特征是一次只有一個成員是有效的并且分配的內存與必要內存最多的成員匹配(也要考慮內存對齊).所有成員都存儲在內存的同一個位置,根據必要存儲不同的值.當你必要 lval 的時候,它存儲的是有符號整形,必要 dval 時,會存儲雙精度浮點數.PHP應用
必要指出的是是聯合體中當前存儲的數據類型會記錄到 type 字段,用一個整型來標記:PHP應用
#define IS_NULL???? 0????? /* Doesn't use value */
#define IS_LONG???? 1????? /* Uses lval */
#define IS_DOUBLE?? 2????? /* Uses dval */
#define IS_BOOL???? 3????? /* Uses lval with values 0 and 1 */
#define IS_ARRAY??? 4????? /* Uses ht */
#define IS_OBJECT?? 5????? /* Uses obj */
#define IS_STRING?? 6????? /* Uses str */
#define IS_RESOURCE 7????? /* Uses lval, which is the resource ID */
/* Special types used for late-binding of constants */
#define IS_CONSTANT 8
#define IS_CONSTANT_AST 9PHP應用
PHP5 中的引用計數PHP應用
在PHP5中,zval 的內存是單獨從堆(heap)中分配的(有少數例外情況),PHP 需要知道哪些 zval 是正在使用的,哪些是需要釋放的.所以這就需要用到引用計數:zval 中 refcount__gc 的值用于保留 zval 本身被引用的次數,比如 $a = $b = 42 語句中,42 被兩個變量引用,所以它的引用計數就是 2.如果引用計數變成 0,就意味著這個變量已經沒有用了,內存也就可以釋放了.PHP應用
注意這里提及到的引用計數指的不是 PHP 代碼中的引用(使用 &),而是變量的使用次數.后面兩者必要同時出現時會使用『PHP 引用』和『引用』來區分兩個概念,這里先忽略掉 PHP 的部分.PHP應用
一個和引用計數緊密相關的概念是『寫時復制』:對于多個引用來說,zaval 只有在沒有變化的情況下才是共享的,一旦其中一個引用改變 zval 的值,就必要復制("separated")一份 zval,然后修改復制后的 zval.PHP應用
下面是一個關于『寫時復制』和 zval 的銷毀的例子:PHP應用
<?php $a = 42; // $a -> zval_1(type=IS_LONG, value=42, refcount=1) $b = $a; // $a, $b -> zval_1(type=IS_LONG, value=42, refcount=2) $c = $b; // $a, $b, $c -> zval_1(type=IS_LONG, value=42, refcount=3) // 下面幾行是關于 zval 分離的 $a += 1; // $b, $c -> zval_1(type=IS_LONG, value=42, refcount=2) // $a -> zval_2(type=IS_LONG, value=43, refcount=1) unset($b); // $c -> zval_1(type=IS_LONG, value=42, refcount=1) // $a -> zval_2(type=IS_LONG, value=43, refcount=1) unset($c); // zval_1 is destroyed, because refcount=0 // $a -> zval_2(type=IS_LONG, value=43, refcount=1)
引用計數有個致命的問題:無法檢查并釋放循環引用(使用的內存).為了解決這問題,PHP 使用了循環回收的辦法.當一個 zval 的計數減一時,就有可能屬于循環的一部分,這時將 zval 寫入到『根緩沖區』中.當緩沖區滿時,潛在的循環會被打上標記并進行回收.PHP應用
因為要支持循環回收,實際使用的 zval 的布局實際上如下:PHP應用
typedef struct _zval_gc_info { zval z; union { gc_root_buffer *buffered; struct _zval_gc_info *next; } u; } zval_gc_info;
zval_gc_info 布局體中嵌入了一個正常的 zval 布局,同時也增加了兩個指針參數,但是共屬于同一個聯合體 u,所以實際使用中只有一個指針是有用的.buffered 指針用于存儲 zval 在根緩沖區的引用地址,所以如果在循環回收執行之前 zval 已經被銷毀了,這個字段就可能被移除了.next 在回收銷毀值的時候使用,這里不會深入.PHP應用
修改動機PHP應用
下面說說關于內存使用上的情況,這里說的都是指在 64 位的系統上.首先,由于 str 和 obj 占用的大小一樣, zvalue_value 這個聯合體占用 16 個字節(bytes)的內存.整個 zval 結構體占用的內存是 24 個字節(考慮到內存對齊),zval_gc_info 的大小是 32 個字節.綜上,在堆(相對于棧)分配給 zval 的內存必要額外的 16 個字節,所以每個 zval 在不同的地方一共必要用到 48 個字節(要理解上面的計算方式必要注意每個指針在 64 位的系統上也必要占用 8 個字節).PHP應用
在這點上不管從什么方面去考慮都可以認為 zval 的這種設計效率是很低的.比如 zval 在存儲整型的時候自己只需要 8 個字節,即使考慮到需要存一些附加信息以及內存對齊,額外 8 個字節應該也是足夠的.PHP應用
在存儲整型時原來確實需要 16 個字節,但是實際上還有 16 個字節用于引用計數、16 個字節用于循環回收.所以說 zval 的內存分配和釋放都是消耗很大的操作,我們有必要對其進行優化.PHP應用
從這個角度思考:一個整型數據真的必要存儲引用計數、循環回收的信息并且單獨在堆上分配內存嗎?答案是當然不,這種處理方式一點都不好.PHP應用
這里總結一下 PHP5 中 zval 實現方式存在的主要問題:PHP應用
zval 總是單獨從堆中分配內存;PHP應用
zval 總是存儲引用計數和循環回收的信息,即使是整型這種可能并不需要此類信息的數據;
在使用對象或者資源時,直接引用會導致兩次計數(原因會在下一部分講);
某些間接拜訪需要一個更好的處理方式.比如現在拜訪存儲在變量中的對象間接使用了四個指針(指針鏈的長度為四).這個問題也放到下一部分討論;
直接計數也就意味著數值只能在 zval 之間共享.如果想在 zval 和 hashtable key 之間共享一個字符串就不行(除非 hashtable key 也是 zval).PHP應用
PHP7 中的 zvalPHP應用
在 PHP7 中 zval 有了新的實現方式.最基礎的變化就是 zval 需要的內存不再是單獨從堆上分配,不再本身存儲引用計數.復雜數據類型(比如字符串、數組和對象)的引用計數由其自身來存儲.這種實現方式有以下好處:PHP應用
簡單數據類型不需要單獨分配內存,也不需要計數;
不會再有兩次計數的情況.在對象中,只有對象自身存儲的計數是有效的;
由于現在計數由數值自身存儲,所以也就可以和非 zval 結構的數據共享,比如 zval 和 hashtable key 之間;
間接拜訪需要的指針數減少了.
PHP應用
我們看看現在 zval 布局體的定義(現在在 zend_types.h 文件中):PHP應用
struct _zval_struct { zend_value value; /* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t var_flags; uint32_t next; /* hash collision chain */ 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; };
結構體的第一個元素沒太大變化,仍然是一個 value 聯合體.第二個成員是由一個表示類型信息的整型和一個包含四個字符變量的結構體組成的聯合體(可以忽略 ZEND_ENDIAN_LOHI_4 宏,它只是用來辦理跨平臺大小端問題的).這個子結構中比較重要的部分是 type(和以前類似)和 type_flags,這個接下來會解釋.PHP應用
上面這個地方也有一點小問題:value 原來應該占 8 個字節,但是由于內存對齊,哪怕只增加一個字節,實際上也是占用 16 個字節(使用一個字節就意味著需要額外的 8 個字節).但是顯然我們并不需要 8 個字節來存儲一個 type 字段,所以我們在 u1 的后面增加了了一個名為 u2 的聯合體.默認情況下是用不到的,需要使用的時候可以用來存儲 4 個字節的數據.這個聯合體可以滿足不同場景下的需求.PHP應用
PHP7 中 value 的布局定義如下:PHP應用
typedef union _zend_value { zend_long lval; /* long value */ double dval; /* double value */ zend_refcounted *counted; zend_string *str; zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
首先必要注意的是現在 value 聯合體必要的內存是 8 個字節而不是 16.它只會直接存儲整型(lval)或者浮點型(dval)數據,其他情況下都是指針(上面提到過,指針占用 8 個字節,最下面的結構體由兩個 4 字節的無符號整型組成).上面所有的指針類型(除了特殊標記的)都有一個同樣的頭(zend_refcounted)用來存儲引用計數:PHP應用
typedef struct _zend_refcounted_h { uint32_t refcount; /* reference counter 32-bit */ union { struct { ZEND_ENDIAN_LOHI_3( zend_uchar type, zend_uchar flags, /* used for strings & objects */ uint16_t gc_info) /* keeps GC root number (or 0) and color */ } v; uint32_t type_info; } u; } zend_refcounted_h;
現在,這個結構體肯定會包括一個存儲引用計數的字段.除此之外還有 type、flags 和 gc_info.type 存儲的和 zval 中的 type 相同的內容,這樣 GC 在不存儲 zval 的情況下單獨使用引用計數.flags 在不同的數據類型中有不同的用途,這個放到下一部分講.PHP應用
gc_info 和 PHP5 中的 buffered 作用相同,不過不再是位于根緩沖區的指針,而是一個索引數字.因為以前根緩沖區的大小是固定的(10000 個元素),所以使用一個 16 位(2 字節)的數字代替 64 位(8 字節)的指針足夠了.gc_info 中同樣包括一個『顏色』位用于回收時標記結點.PHP應用
zval 內存管理PHP應用
上文提到過 zval 需要的內存不再單獨從堆上分配.但是顯然總要有地方來存儲它,所以會存在哪里呢?實際上大多時候它還是位于堆中(所以前文中提到的地方重點不是堆,而是單獨分配),只不過是嵌入到其他的數據結構中的,好比 hashtable 和 bucket 現在就會直接有一個 zval 字段而不是指針.所以函數表編譯變量和對象屬性在存儲時會是一個 zval 數組并得到一整塊內存而不是散落在各處的 zval 指針.之前的 zval * 現在都變成了 zval.PHP應用
之前當 zval 在一個新的地方使用時會復制一份 zval * 并增加一次引用計數.現在就直接復制 zval 的值(忽略 u2),某些情況下可能會增加其布局指針指向的引用計數(如果在進行計數).PHP應用
那么 PHP 怎么知道 zval 是否正在計數呢?不是所有的數據類型都能知道,因為有些類型(好比字符串或數組)并不是總需要進行引用計數.所以 type_info 字段就是用來記錄 zval 是否在進行計數的,這個字段的值有以下幾種情況:PHP應用
#define IS_TYPE_CONSTANT (1<<0) /* special */ #define IS_TYPE_IMMUTABLE (1<<1) /* special */ #define IS_TYPE_REFCOUNTED (1<<2) #define IS_TYPE_COLLECTABLE (1<<3) #define IS_TYPE_COPYABLE (1<<4) #define IS_TYPE_SYMBOLTABLE (1<<5) /* special */
注:在 7.0.0 的正式版本中,上面這一段宏定義的注釋這幾個宏是供 zval.u1.v.type_flags 使用的.這應該是注釋的差錯,因為這個上述字段是 zend_uchar 類型.PHP應用
type_info 的三個主要的屬性就是『可計數』(refcounted)、『可回收』(collectable)和『可復制』(copyable).計數的問題上面已經提過了.『可回收』用于標記 zval 是否參與循環,不如字符串通常是可計數的,但是你卻沒方法給字符串制造一個循環引用的情況.PHP應用
是否可復制用于表示在復制時是否需要在復制時制造(原文用的 "duplication" 來表述,用中文表達出來可能不是很好理解)一份一模一樣的實體."duplication" 屬于深度復制,好比在復制數組時,不僅僅是簡單增加數組的引用計數,而是制造一份全新值一樣的數組.但是某些類型(好比對象和資源)即使 "duplication" 也只能是增加引用計數,這種就屬于不可復制的類型.這也和對象和資源現有的語義匹配(現有,PHP7 也是這樣,不單是 PHP5).PHP應用
下面的表格上標明了不同的類型會使用哪些標志(x 標志的都是有的特性).『簡單類型』(simple types)指的是整型或布爾類型這些不使用指針指向一個結構體的類型.下表中也有『不可變』(immutable)的標志,它用來標志不可變數組的,這個在下一部分再詳述.PHP應用
interned string(保存字符)在這之前沒有提過,其實就是函數名、變量名等無需計數、不可重復的字符串.PHP應用
??????????????? | refcounted | collectable | copyable | immutable
----------------+------------+-------------+----------+----------
simple types??? |??????????? |???????????? |????????? |
string????????? |????? x???? |???????????? |???? x??? |
interned string |??????????? |???????????? |????????? |
array?????????? |????? x???? |????? x????? |???? x??? |
immutable array |??????????? |???????????? |????????? |???? x
object????????? |????? x???? |????? x????? |????????? |
resource??????? |????? x???? |???????????? |????????? |
reference?????? |????? x???? |???????????? |????????? |PHP應用
要理解這一點,我們可以來看幾個例子,這樣可以更好的認識 zval 內存管理是怎么工作的.PHP應用
下面是整數行為模式,在上文中 PHP5 的例子的基礎上進行了一些簡化 :PHP應用
<?php $a = 42; // $a = zval_1(type=IS_LONG, value=42) $b = $a; // $a = zval_1(type=IS_LONG, value=42) // $b = zval_2(type=IS_LONG, value=42) $a += 1; // $a = zval_1(type=IS_LONG, value=43) // $b = zval_2(type=IS_LONG, value=42) unset($a); // $a = zval_1(type=IS_UNDEF) // $b = zval_2(type=IS_LONG, value=42)
這個過程其實挺簡單的.現在整數不再是共享的,變量直接就會分離成兩個單獨的 zval,由于現在 zval 是內嵌的所以也不必要單獨分配內存,所以這里的注釋中使用 = 來表示的而不是指針符號 ->,unset 時變量會被標記為 IS_UNDEF.下面看一下更復雜的情況:PHP應用
<?php $a = []; // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[]) $b = $a; // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=2, value=[]) // $b = zval_2(type=IS_ARRAY) ---^ // zval 分離在這里進行 $a[] = 1 // $a = zval_1(type=IS_ARRAY) -> zend_array_2(refcount=1, value=[1]) // $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[]) unset($a); // $a = zval_1(type=IS_UNDEF), zend_array_2 被銷毀 // $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])
這種情況下每個變量變量有一個單獨的 zval,但是是指向同一個(有引用計數) zend_array 的布局體.修改其中一個數組的值時才會進行復制.這點和 PHP5 的情況類似.PHP應用
類型(Types)PHP應用
我們大概看一下 PHP7 支持哪些類型(zval 使用的類型標志):PHP應用
/* regular data types */ #define IS_UNDEF 0 #define IS_NULL 1 #define IS_FALSE 2 #define IS_TRUE 3 #define IS_LONG 4 #define IS_DOUBLE 5 #define IS_STRING 6 #define IS_ARRAY 7 #define IS_OBJECT 8 #define IS_RESOURCE 9 #define IS_REFERENCE 10 /* constant expressions */ #define IS_CONSTANT 11 #define IS_CONSTANT_AST 12 /* internal types */ #define IS_INDIRECT 15 #define IS_PTR 17
這個列表和 PHP5 使用的類似,不外增加了幾項:PHP應用
IS_UNDEF 用來標記之前為 NULL 的 zval 指針(和 IS_NULL 并不沖突).好比在上面的例子中使用 unset 注銷變量;
IS_BOOL 現在分割成了 IS_FALSE 和 IS_TRUE 兩項.現在布爾類型的標記是直接記錄到 type 中,這么做可以優化類型檢查.不過這個變化對用戶是透明的,還是只有一個『布爾』類型的數據(PHP 腳本中).PHP應用
PHP 引用不再使用 is_ref 來標志,而是使用 IS_REFERENCE 類型.這個也要放到下一部分講;
IS_INDIRECT? 和? IS_PTR 是特殊的內部標志.PHP應用
實際上上面的列表中應該還存在兩個 fake types,這里忽略了.PHP應用
IS_LONG 類型表現的是一個 zend_long 的值,而不是原生的 C 語言的 long 類型.原因是 Windows 的 64 位系統(LLP64)上的 long 類型只有 32 位的位深度.所以 PHP5 在 Windows 上只能使用 32 位的數字.PHP7 允許你在 64 位的操作系統上使用 64 位的數字,即使是在 Windows 上面也可以.PHP應用
zend_refcounted 的內容會在下一部門講.下面看看 PHP 引用的實現.PHP應用
引用PHP應用
PHP7 使用了和 PHP5 中完全不同的辦法來處理 PHP & 符號引用的問題(這個改動也是 PHP7 開發過程中大量 bug 的根源).我們先從 PHP5 中 PHP 引用的實現方式說起.PHP應用
通常情況下, 寫時復制原則意味著當你修改一個 zval 之前必要對其進行分離來保證始終修改的只是某一個 PHP 變量的值.這就是傳值調用的含義.PHP應用
但是使用 PHP 引用時這條規則就不適用了.如果一個 PHP 變量是 PHP 引用,就意味著你想要在將多個 PHP 變量指向同一個值.PHP5 中的 is_ref 標記就是用來注明一個 PHP 變量是不是 PHP 引用,在修改時需不需要進行分離的.好比:PHP應用
<?php $a = []; // $a -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[]) $b =& $a; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[]) $b[] = 1; // $a = $b = zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[1]) // 因為 is_ref 的值是 1, 所以 PHP 不會對 zval 進行分離
但是這個設計的一個很大的問題在于它無法在一個 PHP 引用變量和 PHP 非引用變量之間共享同一個值.好比下面這種情況:PHP應用
<?php $a = []; // $a -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[]) $b = $a; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[]) $c = $b // $a, $b, $c -> zval_1(type=IS_ARRAY, refcount=3, is_ref=0) -> HashTable_1(value=[]) $d =& $c; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[]) // $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[]) // $d 是 $c 的引用, 但卻不是 $a 的 $b, 所以這里 zval 還是需要進行復制 // 這樣我們就有了兩個 zval, 一個 is_ref 的值是 0, 一個 is_ref 的值是 1. $d[] = 1; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[]) // $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[1]) // 因為有兩個分離了的 zval, $d[] = 1 的語句就不會修改 $a 和 $b 的值.
這種行為方式也導致在 PHP 中使用引用比普通的值要慢.好比下面這個例子:PHP應用
<?php $array = range(0, 1000000); $ref =& $array; var_dump(count($array)); // <-- 這里會進行分離
因為 count() 只接受傳值調用,但是 $array 是一個 PHP 引用,所以 count() 在執行之前實際上會有一個對數組進行完整的復制的過程.如果 $array 不是引用,這種情況就不會產生了.PHP應用
現在我們來看看 PHP7 中 PHP 引用的實現.因為 zval 不再單獨分配內存,也就沒方法再使用和 PHP5 中相同的實現了.所以增加了一個 IS_REFERENCE 類型,并且專門使用 zend_reference 來存儲引用值:PHP應用
struct _zend_reference { zend_refcounted gc; zval val; };
本色上 zend_reference 只是增加了引用計數的 zval.所有引用變量都會存儲一個 zval 指針并且被標記為 IS_REFERENCE.val 和其他的 zval 的行為一樣,尤其是它也可以在共享其所存儲的復雜變量的指針,比如數組可以在引用變量和值變量之間共享.PHP應用
我們還是看例子,這次是 PHP7 中的語義.為了簡潔明了這里不再單獨寫出 zval,只展示它們指向的布局體:PHP應用
<?php $a = []; // $a -> zend_array_1(refcount=1, value=[]) $b =& $a; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[]) $b[] = 1; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[1])
上面的例子中進行引用傳遞時會創建一個 zend_reference,注意它的引用計數是 2(因為有兩個變量在使用這個 PHP 引用).但是值自己的引用計數是 1(因為 zend_reference 只是有一個指針指向它).下面看看引用和非引用混合的情況:PHP應用
<?php $a = []; // $a -> zend_array_1(refcount=1, value=[]) $b = $a; // $a, $b, -> zend_array_1(refcount=2, value=[]) $c = $b // $a, $b, $c -> zend_array_1(refcount=3, value=[]) $d =& $c; // $a, $b -> zend_array_1(refcount=3, value=[]) // $c, $d -> zend_reference_1(refcount=2) ---^ // 注意所有變量共享同一個 zend_array, 即使有的是 PHP 引用有的不是 $d[] = 1; // $a, $b -> zend_array_1(refcount=2, value=[]) // $c, $d -> zend_reference_1(refcount=2) -> zend_array_2(refcount=1, value=[1]) // 只有在這時進行賦值的時候才會對 zend_array 進行賦值
這里和 PHP5 最大的不同就是所有的變量都可以共享同一個數組,即使有的是 PHP 引用有的不是.只有當其中某一部分被修改的時候才會對數組進行分離.這也意味著使用 count() 時即使給其傳遞一個很大的引用數組也是平安的,不會再進行復制.不過引用仍然會比普通的數值慢,因為存在需要為 zend_reference 結構體分配內存(間接)并且引擎本身處理這一塊兒也不快的的原因.PHP應用
結語PHP應用
總結一下 PHP7 中最重要的改變就是 zval 不再單獨從堆上分配內存并且不本身存儲引用計數.需要使用 zval 指針的復雜類型(比如字符串、數組和對象)會本身存儲引用計數.這樣就可以有更少的內存分配操作、更少的間接指針使用以及更少的內存分配.PHP應用
在下篇文章給大家介紹變量在 PHP7 內部的實現(二),感興趣的朋友繼續存眷.PHP應用
《PHP學習:變量在 PHP7 內部的實現(一)》是否對您有啟發,歡迎查看更多與《PHP學習:變量在 PHP7 內部的實現(一)》相關教程,學精學透。維易PHP學院為您提供精彩教程。
轉載請注明本頁網址:
http://www.snjht.com/jiaocheng/8195.html