PHP7內(nèi)核(六):變量之zval
記得網(wǎng)上流傳甚廣的段子“PHP是世界上最好的語言”,暫且不去討論是否言過其實,但至少PHP確實有獨特優(yōu)勢的,比如它的弱類型,即只需要$符號即可聲明變量,使得PHP入手門檻極低,成為大家所青睞的Web服務(wù)端語言。那么它的變量是如何實現(xiàn)的呢?我們今天就來學(xué)習(xí)一下PHP的基本變量。
一、引言
PHP的變量存儲在zval結(jié)構(gòu)體中,在執(zhí)行階段中編譯為op_array時就能看到zval的身影。結(jié)構(gòu)體定義在Zend/zend_types.h中,定義內(nèi)容如下所示:
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) /* 保留字段 */
} 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;
};
二、結(jié)構(gòu)體剖析
2.1、zend_value
結(jié)構(gòu)體的第一個變量是zend_value,顧名思義,它其實也是一個結(jié)構(gòu)體,用于存放變量的值,比如整型、浮點型、引用計數(shù)、字符串、數(shù)組、對象、資源等。zend_value定義了眾多類型的指針,但這些類型并不都是變量的類型,有些是給內(nèi)核自己使用的,比如指針ast、zv、ptr。
typedef union _zend_value {
zend_long lval; /* 整型 */
double dval; /* 浮點型 */
zend_refcounted *counted; /* 引用計數(shù) */
zend_string *str; /* 字符串 */
zend_array *arr; /* 數(shù)組 */
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是一個聯(lián)合體,它聯(lián)合了結(jié)構(gòu)體v和整型type_info。下面我們先來看一下結(jié)構(gòu)體v的構(gòu)成。
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;
2.2.1、type
type是指變量的類型,剛在2.1中講到了zend_value是用來存儲變量的值,所以也應(yīng)該有地方存儲變量的類型,而這就是type的職責(zé)。以下是PHP定義的所有變量類型,有我們熟知的布爾、NULL、浮點、數(shù)組、字符串等類型。也有陌生的undef、indirect、ptr類型,變量類型在下一章中詳解,這里不再贅述。
/* 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
/* fake types */
#define _IS_BOOL 13
#define IS_CALLABLE 14
/* internal types */
#define IS_INDIRECT 15
#define IS_PTR 17
2.2.2、type_flags
可以把它理解為子類型,上面提到了變量的類型,這個是針對不同類型的子類型或標(biāo)記,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) /* 需要引用計數(shù)的類型 */
#define IS_TYPE_COLLECTABLE (1<<3) /* 可能包含循環(huán)引用的類型 */
#define IS_TYPE_COPYABLE (1<<4) /* 可被復(fù)制的類型 */
#define IS_TYPE_SYMBOLTABLE (1<<5) /* 符號表類型 */
2.2.3、const_flags
常量類型的標(biāo)記,對應(yīng)的屬性為:
/* zval.u1.v.const_flags */
#define IS_CONSTANT_UNQUALIFIED 0x010
#define IS_LEXICAL_VAR 0x020
#define IS_LEXICAL_REF 0x040
#define IS_CONSTANT_CLASS 0x080 /* __CLASS__ in trait */
#define IS_CONSTANT_IN_NAMESPACE 0x100 /* used only in opline->extended_value */
2.2.4、type_info
type_info與結(jié)構(gòu)體v共用內(nèi)存,修改type_info等同于修改結(jié)構(gòu)體v的值,所以type_info是v中四個char的組合。
2.3、u2
本來使用u1和zend_value就可以表示變量的,沒有必要定義u2,但是我們來看一下,如果沒有u2,在內(nèi)存對齊的情況下zval內(nèi)存大小為16個字節(jié),當(dāng)聯(lián)合了u2后依然是占用16個字節(jié)。既然有或沒有占用內(nèi)存大小相同,不如用它來記錄一些附屬信息。下面我們來看下u2都存儲了哪些內(nèi)容。
2.3.1、next
用來解決哈希沖突問題,記錄沖突的下一個元素位置。
2.3.2、cache_slot
運行時緩存,在執(zhí)行函數(shù)時回去緩存中查找,若緩存中沒有則到全局function表中查找。
2.3.3、lineno
文件執(zhí)行的行號,應(yīng)用在AST節(jié)點上。Zend引擎在詞法和語法解析時會把當(dāng)前執(zhí)行的文件行號記錄下來,記錄在zend_ast中的lineno中。
2.3.4、num_args
函數(shù)調(diào)用時傳入函數(shù)的參數(shù)個數(shù)。
2.3.5、fe_pos
用于遍歷數(shù)組時記錄當(dāng)前遍歷的位置,比如每次執(zhí)行foreach時fe_pos都會加一,當(dāng)再次調(diào)用foreach進(jìn)行遍歷時,fe_post會進(jìn)行重置。
2.3.6、fe_iter_idx
這個與fe_pos類似,只不過它是針對對象的。對象的屬性也是HashTable,傳入的參數(shù)是對象時,會獲取對象的屬性,所以遍歷對象就是在變量對象的屬性。
三、參考文獻(xiàn)
- 《PHP7內(nèi)核剖析》
- 《PHP7底層設(shè)計和源碼實現(xiàn)》
- 深入理解PHP7內(nèi)核之zval

浙公網(wǎng)安備 33010602011771號