變數的基礎結構
我們都知道PHP的變數是弱型別的,宣告的時候無需指定型別。那麼這裡面具體是怎麼實現的呢?這就得從變數的基礎結構說起了。
zval的實現
在原始碼檔案 zend_type.h 中,可以看到 zval 的定義:
typedef struct _zval_struct zval;
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 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 */
uint32_t access_flags; /* class constant access flags */
uint32_t property_guard; /* single property guard */
uint32_t extra; /* not further specified */
} u2;
}
zval 的結構由一個儲存變數型別的值或指標的 union 聯合體 zend_value 以及兩個 union 聯合體 u1 和 u2 組成
- u1
u1的作用是用來儲存變數型別及其資訊,其裡面的欄位用處如下:
type:記錄變數型別。 即可通過 u2.v.type 來訪問到
type_flags:對應變數特有型別的標記(如常量型別,需引用計數型別,不可變型別),不同型別的變數對應的 flag 不一樣。
const_flags:常量型別的標記
reserved:保留欄位
- u2
u2 主要是輔助作用,由於結構體的記憶體對齊,所以 u2 的的這塊空間有或者沒有 u2 都是已經佔據空間了,所以就利用起來。u2的輔助欄位裡面記錄了很多型別資訊,這些資訊對內部功能有很大的好處,或提升快取友好性或減少了記憶體定址的操作。這裡介紹其中部分欄位。
next:用來解決雜湊衝突問題(雜湊衝突這個目前還不懂),記錄衝突的下一個元素位置。
cache_slot:執行時快取。在執行函式時會優先去快取中查詢,若快取中沒有,再去全域性的 function 表中查詢。
num_args:函式呼叫時傳入引數的個數
access_flags:物件類的訪問標識,如public protected private 這些。
- zend_value
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;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
從 zend__value 中可以看出,long、double 型別直接儲存值,而其它型別都為指標,指向各自的結構。所以,由於 zval 這樣的結構,PHP 變數在宣告的時候不用顯示的指定其型別,因為不管你賦給變數什麼型別的值,它都能幫你找到對應的儲存結構。
以值為字串的變數為例,其結構是這樣的:
PHP5 與 PHP7 的 zval 結構對比
- PHP5
- PHP7
可以看到 php7 的 zval 總的只佔 16 個位元組,相比 PHP5 的 zval 所佔用的 48 個位元組節省了很大的記憶體。
此外,在 PHP5 中,所有的變數都在堆中申請,但是對於臨時變數來說,沒有必要在堆中申請。所以在 PHP7 中對此做了優化,臨時變數是直接在棧中申請的。
常見變數型別
下面介紹幾個常見型別的變數結構,其他更多的型別,可自行檢視原始碼。
整型和浮點型
對於整型和浮點型,由於其佔用空間小,在 zval 中是直接儲存的 整型的值是存在 lval 裡,浮點型值則是儲存在 dval 裡。
typedef union _zend_value {
zend_long lval; /* 整型*/
double dval; /* 浮點型 */
...
}
字串
PHP 7 中定義了新的字串結構體。結構如下:
struct _zend_string {
zend_refcounted_h ;
zend_ulong h; /* hash value */
size_t len;
char val[1];
};
上面各個欄位的意思:
gc: 變數引用資訊,所有用到引用計數的變數型別都會有這個結構。
h: 雜湊值,陣列中計算索引時會用到。(據說這個操作為 PHP7 提高了 5% 的效能)
len: 字串長度,通過這個值保證二進位制安全
val: 字串內容,變長struct,分配時按len長度申請記憶體
陣列
array 是 PHP 中非常強大的一個資料結構,它的底層實現就是普通的有序HashTable,這裡簡單看下它的結構。後續再具體深入。
typedef struct _zend_array HashTable;
struct _zend_array {
zend_refcounted_h gc;
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar flags,
zend_uchar nApplyCount,
zend_uchar nIteratorsCount,
zend_uchar consistency)
} v;
uint32_t flags;
} u;
uint32_t nTableMask;
Bucket *arData;
uint32_t nNumUsed;
uint32_t nNumOfElements;
uint32_t nTableSize;
uint32_t nInternalPointer;
zend_long nNextFreeElement;
dtor_func_t pDestructor;
}
物件
PHP7 的物件結構也是重新設計了,和 PHP5 的實現有了很大的不同。
struct _zend_object {
zend_refcounted_h gc;
uint32_t handle;
zend_class_entry *ce;
const zend_object_handlers *handlers;
HashTable *properties;
zval properties_table[1];
};
這裡介紹下其中幾個欄位:
gc:gc頭部
*ce:物件對應的 class 類
*properties :HashTable結構,key 為物件的屬性名,value 是屬性值在properties_tables陣列中的偏移量,通過偏移量在 properties_talbe 找到對應的屬性值。
properties_talbe[1]:儲存物件的屬性值
ok,先寫這到這裡。
參考資料
《PHP7 底層設計與原始碼實現》