以前面試的時候面試官問過php中變量是如何實現的,遺憾的是隻答道了大概是用結構體實現的。這篇文章是谷歌以後以爲總結 的比較到位的,故轉載進而學習之。html
PHP中的數據類型node
相對於 C、 C++、 Java等其餘編程語言,PHP 是一個弱類型的語言,意味着當咱們要使用一個變量時,不須要去聲明它的類型。這個特性給咱們帶來了不少便利,同時有時也會帶來一些陷阱。那麼,PHP 是真的沒有數據類型這個說法嗎?git
固然不是。在 PHP 官方文檔中將 PHP 中的變量劃分爲三類:標量類型、複雜類型和特殊類型。標量類型包括布爾型(bool)、整型(int)、浮點型(float)和字符串(string);複雜類型包括數組(array)和對象(object);特殊類型包括 NULL 和資源(resource)。因此說 PHP 的變量細分的話,有 8 種數據類型。github
總所周知,PHP 的底層是用 C 語言實現的。咱們的 PHP 腳本會通過 Zend 引擎解析爲 C 代碼再執行。那麼,一個 PHP 的變量,在 C 語言上是怎麼表示的呢?它最終會被解析成什麼樣呢?web
答案就是 zval。 無論什麼類型的 PHP 變量,在 PHP 源代碼中統一用一個叫作 zval 的結構表示。 zval 能夠看作是 PHP 變量在 C 代碼中的容器,它存儲了這個變量的值、類型等相關信息。面試
那麼咱們就看一下 zval 的基本結構(須要一點 C 語言的基本知識)。算法
zval的基本結構編程
在 PHP 源代碼中 zval 這個結構是一個名叫 _zval_struct
的結構體(struct),具體定義在源代碼的 Zend/zend.h
文件中,下面是相關代碼的摘錄:數組
struct _zval_struct {
zvalue_value value; /* value */
zend_uint refcount__gc; /* value of ref count */
zend_uchar type; /* active type */
zend_uchar is_ref__gc; /* if it is a ref variable */
};
typedef struct _zval_struct zval;
也就是說,在 PHP 的源碼中,就用這一個結構體表示 PHP 中各類類型的變量,而且還能夠實現其餘的一些功能,例如垃圾回收(GC:Grabage Collection)。
能夠看到它由 4 個字段構成,分別表示這個變量的某個信息。
value 用來表示變量的實際值,具體來講它是一個 zvalue_value 的聯合體(union):
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct { /* string */
char *val;
int len;
} str;
HashTable *ht; /* hash table value,used for array */
zend_object_value obj; /* object */
} zvalue_value;
能夠看到 _zvalue_value 中只有 5 個字段,可是 PHP 中有 8 種數據類型,那麼如何用 5 個字段表示 8 種類型呢?
這算是 PHP 設計比較巧妙的一個地方,它經過複用字段達到了減小字段的目的。例如,在 PHP 內部布爾型、整型及資源(只要存儲資源的標識符便可)都是經過 lval 字段存儲的;dval 用於存儲浮點型;str 存儲字符串;ht 存儲數組(注意 PHP 中的數組實際上是哈希表);而 obj 存儲對象類型;若是全部字段所有置爲 0 或 NULL則表示 PHP 中的 NULL,這樣就達到了用 5 個字段存儲 8 種類型的值。
從它的後綴 gc 能夠看到,這個字段是和垃圾回收相關的。
它其實是一個計數器,用來保存有多少變量指向該zval。在變量生成時,置爲1,也就是 refcount = 1。
對變量進行不一樣的操做會改變它的值。典型的賦值操做如 b 會使 refcount 加 1,而 unset() 操做會相應的減 1。
經過判斷它的值能夠進行垃圾回收。在 PHP5.3 以前,使用引用計數的機制來實現 GC:若是一個 zval 的 refcount 減爲 0,那麼 Zend 引擎會認爲沒有任何變量指向該 zval,就會釋放該 zval 所佔的內存空間。但僅僅使用引用計數機制沒法釋放掉循環引用的 zval,這是就會致使內存泄露(Memory Leak)。
在 5.3 之前,這個字段的名字還叫作 refcount,5.3 之後,在引入新的垃圾回收算法來對付循環引用,做者加入了大量的宏來操做 refcount,爲了能讓錯誤更快的顯現,因此更名爲 refcount__gc, 迫使你們都使用宏來操做 refcount。
相似的, 還有第四個字段 is_ref, 這個值表示了 PHP 中的一個類型是不是引用。
想了解 PHP 的垃圾回收機制,能夠參考這篇博客:PHP的垃圾回收機制
注:變量,也能夠被稱爲符號,symbol。全部的符號都存在符號表(symbol table)中, 不一樣的做用域使用不一樣的符號表,關於這一點,這篇博客進行了講解。
這個字段用於代表變量屬於 PHP 8 種類型的哪一種。在 zend 內部,這些類型對應於下面的宏(代碼位置 phpsrc/Zend/zend.h):
#define IS_NULL 0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_ARRAY 9
#define IS_CALLABLE 10
這個字段用於標記變量是不是引用變量。對於普通的變量,該值爲 0,而對於引用型的變量,該值爲 1。這個變量會影響 zval 的共享、分離等。它也和 PHP 的垃圾回收有關。
上述的 zval 結構,隨着時間的發展,暴露出許多問題,例如佔用空間大(24 字節)、不支持拓展、 對象和引用效率差等,因此在 PHP7 的時候,對 zval 進行了較大的改變,如今它的結構是這樣的:
struct _zval_struct {
union {
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;
} 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;
};
雖然看起來變得好大,但其實仔細看,它的字段都是聯合體,這個新的 zval 在 64 位環境下,只須要 16 個字節(2 個指針 size)。PHP7 中的 zval,已經變成了一個值指針,它要麼保存着原始值,要麼保存着指向一個保存原始值的指針。
這部份內容來自鳥哥的GitHub。