這篇文章主要介紹的內容是關於PHP核心之zval,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
原文地址
作者:Twei 主頁
之前面試的時候面試官問過php中變數是如何實現的,遺憾的是只答道了大概是用結構體實現的。這篇文章是谷歌之後覺得總結 的比較到位的,故轉載進而學習之。
PHP中的資料型別
#相對於C、 C 、 Java等其他程式語言,PHP 是一個弱型別的語言,表示當我們要使用一個變數時,不需要去宣告它的型別。這個特性為我們帶來了許多便利,同時有時也會帶來一些陷阱。那麼,PHP 是真的沒有資料型這個說法嗎?
當然不是。在 PHP 官方文件中將 PHP 中的變數劃分為三類:標量類型、複雜型別和特殊型別。標量類型包括布林型(bool)、整數型(int)、浮點型(float)和字串(string);複雜型別包括陣列(array)和物件(object);特殊型別包括NULL 和資源(resource) 。所以說 PHP 的變數細分的話,有 8 種資料型別。
總所周知,PHP 的底層是用 C 語言實現的。我們的 PHP 腳本會經過 Zend 引擎解析為 C 程式碼再執行。那麼,一個 PHP 的變量,在 C 語言上是怎麼表示的呢?它最終會被解析成什麼樣子呢?
答案就是 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。
zval 是一种 C 语言实现的数据结构,功能是作为 PHP 变量的容器;
它保存了变量的各种信息(如类型和值),并为其他功能(如垃圾回收)提供支持;
在不同的 PHP 版本中,它的结构不同。PHP7 的 zval 占 16 个字节,PHP5 的要占 24 个字节。
PHP内核探索之变量(1)变量的容器-Zval
PHP垃圾回收深入理解
深入理解PHP7之zval
相关推荐:
以上是PHP內核之zval的詳細內容。更多資訊請關注PHP中文網其他相關文章!