PHP7內核(六):變數之zval

記得網上流傳甚廣的段子「PHP是世界上最好的語言」,暫且不去討論是否言過其實,但至少PHP確實有獨特優勢的,比如它的弱類型,即只需要$符號即可聲明變數,使得PHP入手門檻極低,成為大家所青睞的Web服務端語言。那麼它的變數是如何實現的呢?我們今天就來學習一下PHP的基本變數。

一、引言

PHP的變數存儲在zval結構體中,在執行階段中編譯為op_array時就能看到zval的身影。結構體定義在Zend/zend_types.h中,定義內容如下所示:

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;  };

二、結構體剖析

2.1、zend_value

結構體的第一個變數是zend_value,顧名思義,它其實也是一個結構體,用於存放變數的值,比如整型、浮點型、引用計數、字元串、數組、對象、資源等。zend_value定義了眾多類型的指針,但這些類型並不都是變數的類型,有些是給內核自己使用的,比如指針ast、zv、ptr。

typedef union _zend_value {  	zend_long         lval;				/* 整型 */  	double            dval;				/* 浮點型 */  	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_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是用來存儲變數的值,所以也應該有地方存儲變數的類型,而這就是type的職責。以下是PHP定義的所有變數類型,有我們熟知的布爾、NULL、浮點、數組、字元串等類型。也有陌生的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

可以把它理解為子類型,上面提到了變數的類型,這個是針對不同類型的子類型或標記,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_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與結構體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,傳入的參數是對象時,會獲取對象的屬性,所以遍歷對象就是在變數對象的屬性。

三、參考文獻