跟廠長學PHP核心7(六):變數之zval
記得網上流傳甚廣的段子“PHP是世界上最好的語言”,暫且不去討論是否言過其實,但至少PHP確實有獨特優勢的,比如它的弱型別,即只需要$符號即可宣告變數,使得PHP入手門檻極低,成為大家所青睞的Web服務端語言。那麼它的變數是如何實現的呢?我們今天就來學習一下PHP的基本變數。
一、引言
PHP的變數儲存在zval結構體中,在執行階段中編譯為op_array時就能看到zval的身影。結構體定義在Zend/zend_types.h
中,定義內容如下所示:
struct _zval_struct { zend_valuevalue;/* value */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchartype,/* active type */ zend_uchartype_flags, zend_ucharconst_flags, zend_ucharreserved)/* 保留欄位 */ } v; uint32_t type_info; } u1; union { uint32_tvar_flags; uint32_tnext;/* hash collision chain */ uint32_tcache_slot;/* literal cache slot */ uint32_tlineno;/* line number (for ast nodes) */ uint32_tnum_args;/* arguments number for EX(This) */ uint32_tfe_pos;/* foreach position */ uint32_tfe_iter_idx;/* foreach iterator index */ } u2; };
二、結構體剖析
2.1、zend_value
結構體的第一個變數是zend_value,顧名思義,它其實也是一個結構體,用於存放變數的值,比如整型、浮點型、引用計數、字串、陣列、物件、資源等。zend_value
定義了眾多型別的指標,但這些型別並不都是變數的型別,有些是給核心自己使用的,比如指標ast、zv、ptr。
typedef union _zend_value { zend_longlval;/* 整型 */ doubledval;/* 浮點型 */ zend_refcounted*counted;/* 引用計數 */ zend_string*str;/* 字串 */ zend_array*arr;/* 陣列 */ zend_object*obj;/* 物件 */ zend_resource*res;/* 資源 */ zend_reference*ref;/* 引用 */ zend_ast_ref*ast;/* 抽象語法樹 */ zval*zv;/* zval型別 */ void*ptr;/* 指標型別 */ zend_class_entry *ce;/* class型別 */ zend_function*func;/* function型別 */ struct { uint32_t w1; uint32_t w2; } ww; } zend_value;
2.2、u1
u1是一個聯合體,它聯合了結構體v
和整型type_info
。下面我們先來看一下結構體v
的構成。
union { struct { ZEND_ENDIAN_LOHI_4( zend_uchartype,/* active type */ zend_uchartype_flags, zend_ucharconst_flags, zend_ucharreserved)/* call info for EX(This) */ } v; uint32_t type_info; } u1;
2.2.1、type
type是指變數的型別,剛在2.1
中講到了zend_value是用來儲存變數的值,所以也應該有地方儲存變數的型別,而這就是type的職責。以下是PHP定義的所有變數型別,有我們熟知的布林、NULL、浮點、陣列、字串等型別。也有陌生的undef、indirect、ptr型別,變數型別在下一章中詳解,這裡不再贅述。
/* regular data types */ #define IS_UNDEF0 #define IS_NULL1 #define IS_FALSE2 #define IS_TRUE3 #define IS_LONG4 #define IS_DOUBLE5 #define IS_STRING6 #define IS_ARRAY7 #define IS_OBJECT8 #define IS_RESOURCE9 #define IS_REFERENCE10 /* constant expressions */ #define IS_CONSTANT11 #define IS_CONSTANT_AST12 /* fake types */ #define _IS_BOOL13 #define IS_CALLABLE14 /* internal types */ #define IS_INDIRECT15 #define IS_PTR17
2.2.2、type_flags
可以把它理解為子型別,上面提到了變數的型別,這個是針對不同型別的子型別或標記,type_flags
一共有以下6種。
/* zval.u1.v.type_flags */ #define IS_TYPE_CONSTANT(1<<0)/* 常量 */ #define IS_TYPE_IMMUTABLE(1<<1)/* 不可變的型別 */ #define IS_TYPE_REFCOUNTED(1<<2)/* 需要引用計數的型別 */ #define IS_TYPE_COLLECTABLE(1<<3)/* 可能包含迴圈引用的型別 */ #define IS_TYPE_COPYABLE(1<<4)/* 可被複制的型別 */ #define IS_TYPE_SYMBOLTABLE(1<<5)/* 符號表型別 */
2.2.3、const_flags
常量型別的標記,對應的屬性為:
/* zval.u1.v.const_flags */ #define IS_CONSTANT_UNQUALIFIED0x010 #define IS_LEXICAL_VAR0x020 #define IS_LEXICAL_REF0x040 #define IS_CONSTANT_CLASS0x080/* __CLASS__ in trait */ #define IS_CONSTANT_IN_NAMESPACE0x100/* used only in opline->extended_value */
2.2.4、type_info
type_info與結構體v共用記憶體,修改type_info等同於修改結構體v的值,所以type_info是v中四個char的組合。
2.3、u2
本來使用u1和zend_value就可以表示變數的,沒有必要定義u2,但是我們來看一下,如果沒有u2,在記憶體對齊的情況下zval記憶體大小為16個位元組,當聯合了u2後依然是佔用16個位元組。既然有或沒有佔用記憶體大小相同,不如用它來記錄一些附屬資訊。下面我們來看下u2都儲存了哪些內容。
2.3.1、next
用來解決雜湊衝突問題,記錄衝突的下一個元素位置。
2.3.2、cache_slot
執行時快取,在執行函式時回去快取中查詢,若快取中沒有則到全域性function表中查詢。
2.3.3、lineno
檔案執行的行號,應用在AST節點上。Zend引擎在詞法和語法解析時會把當前執行的檔案行號記錄下來,記錄在zend_ast中的lineno中。
2.3.4、num_args
函式呼叫時傳入函式的引數個數。
2.3.5、fe_pos
用於遍歷陣列時記錄當前遍歷的位置,比如每次執行foreach時fe_pos都會加一,當再次呼叫foreach進行遍歷時,fe_post會進行重置。
2.3.6、fe_iter_idx
這個與fe_pos類似,只不過它是針對物件的。物件的屬性也是HashTable,傳入的引數是物件時,會獲取物件的屬性,所以遍歷物件就是在變數物件的屬性。
三、參考文獻
- 《PHP7核心剖析》
- 《PHP7底層設計和原始碼實現》
- ofollow,noindex" target="_blank">深入理解PHP7核心之zval