工做+實習快一年了,搞php後端開發,一直很迷茫怎麼提升本身,就先從php源碼開始吧,本人比較菜,本文章寫的比較趕時間,因此有什麼錯誤或者漏掉的地方,望各位大神指正,多交流才能成長嘛,嘿嘿。
本文主要是針對php7,php5的話能夠移步到慶哥的博客看,還有就是小菜我讀的是《php7內核剖析》這本書。
接下來我會使用到xdebug來調試php源碼php
本文有參照ohmygirl博客中的部份內容以及代碼。html
本文所用環境爲windows,php7.0.10node
php7和php5不一樣的地方有不少,zval,zend_value結構就是其中之一git
在php7中
zval定義在zend_types.h中github
在zval這個結構體重包含三個部分 zend_value(存儲實際的內容),u1,u2兩個聯合體,其中u1主要存儲變量相關的一下屬性,而u2則是對u1的一些補充,例如當用到數組的時候,會用到u2.next來解決key哈希後出現的hash衝突segmentfault
struct _zval_struct { zend_value value; /* 存儲變量的實際內容 */ union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* 存儲變量的類型 */ zend_uchar type_flags, /* 用於標識變量狀態,例如GC方面的管理,經過設置爲IS_TYPE_COLLECTABLE 則變量會被收集到GC中回收垃圾的buffer緩存區中 */ 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; };
zend_uchar type: 如下爲外部使用的變量類型windows
#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
php7中zend_value結構後端
typedef union _zend_value { 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; } zend_value;
這裏咱們先解釋一下php7的zval,zend_valu中重要的的幾個變量數組
zval中:緩存
(1)zend_uchar type 這個是用來表示當前變量(例如 $a)是什麼類型的變量($a是string類型?仍是int類型?仍是引用類型....)
zend_value中:
(1)zend_refcounted *counted; 表示引用計數的次數, 何爲引用計數? 就是zend_value變量被zval引用的次數,例如咱們$a=「abcs」;$c=$a;$b=$a;此時counted=3,以下圖,其中refcount也是實現GC自動內存回收的基礎,下面會詳細講解
php7中對與變量的實現分如下幾種方式
(1).對於boolen類型,還有null,undefined,這種沒有具體值,只有類型的類型,直接在zval中經過zend_uchar type的類型來判斷,無需經過引用計數來實現。
正是由於沒有經過應用計數來實現,因此它refcount爲0
(2)對於int類型和float類型,由於在zend_value中有zend_long和double來保存數據,以下圖,因此,在賦值的時候就不須要再使用引用計數了,在拷貝的直接進行賦值就好了,這樣作能夠省掉大量引用計數的相關操做
定義一個$a=1,在php內核中zval和zend_value的關係
(3)第三種就是常規的使用引用計數的方式來進行來進行變量的定義。在這些變量的實現中,都是經過指針指向一個具體的數據類型
例如 :
zend_string *str; struct _zend_string { zend_refcounted_h gc; zend_ulong h; /* hash value */ size_t len; char val[1]; };
$假設我定義一個$a="111";其內部的實現就是,注意這裏zend_string中char val[1],主要是由於C語言中字符串是以「0」結尾的,因此在zend_string中$a="111"這個變量值的存儲是char val[4]="111"。
1.普通賦值
前面說到,在php中,定義一個變量$a="444",其實是生成了一個zval,和一個zend_value,而後zval指向這個zend_value來實現對$a="444"的定義的,而後經過refcount來統計引用的次數。
**因此能夠總結出,refcount表示當前有多少個zval指向同一個zend_value**
咱們定義以下:
$a="111";
$b=$a;
固然對於像boole型還有int,double,null變量,他們的直接經過zval保存,不會公用一個zend_value,因此直接使用深拷貝。
至於什麼是深拷貝,什麼是淺拷貝,最直接的區別就是在於有沒有從新生成一個如出一轍的zend_value,詳細請參看深拷貝和淺拷貝。
後面的寫時賦值(COW copy on write)就會使用到深拷貝。
對於php的賦值,實際上並非全部的類型都是同樣的,剛剛也有說到,在php的zval中就有一個專門的字段用於標識當前類型適合哪一種形式的那就是。
zend_uchar type_flags,
| refcounted | collectable | copyable | immutable ----------------+------------+-------------+----------+---------- simple types | | | | string | x | | x | interned string | | | | array | x | x | x | immutable array | | | | x object | x | x | | resource | x | | | reference | x | | |
zend_uchar type_flags這個字段用於標識當前的zval的屬於哪一種賦值方式或者處於哪一種狀態,主要是用於內存方面的管理具體參見內存管理,
2.引用賦值
php中的引用賦值就是咱們經常使用的引用,例如$a=&$b,這樣。
在php7中實現引用的時候,在php中實現引用的時候,會先生成一個zend_reference類型,這個類型中嵌套一個zval,而後這個zval的zend_value會指向以前的以前的那個zval的zend_value,原來的那個zval類型轉換成IS_REFERENCE類型,簡單來講**就是將原有的zval類型轉換成IS_REFERENCE,並新生成zend_reference類型指向原zend的zend_value。(好jb繞,理了很久)。
struct _zend_reference { zend_refcounted_h gc; zval val; };
例如咱們定義一個$a="1111";$b=&$a;它的變換以下圖
$a="111"
$b=&$a;
可能有朋友注意到了,在zend_reference中refcount爲2 ,可是在最後的真實的zend_value爲refcount爲1,這是爲何呢,其實很好理解,前面說過,refcount表示有多少個zval指向該zend_value,zend_reference中只有一個zval指向了zend_value。
中間加一層zend_reference這樣作其實有不少好處,這樣能夠保留原有的zend_value類型不變,爲深拷貝操做提供拷貝條件,下面咱們舉個例子就知道了
是否是一目瞭然,只能說開發內核的那些神牛太牛逼了。。。。。。。
固然關於php中值傳遞不只僅那麼簡單,還有不少很複雜的東西,小菜我也是纔看沒多久,望各位大牛勿噴
寫時複製是一種很重要的優化手段,這裏涉及到深拷貝和淺拷貝的知識,請移步。
關於深拷貝,剛剛上面的這個例子就是很好的說明
所謂深拷貝就是將原有的數據拷貝一份放到獨立分配一個地址空間。
而寫時賦值就是對深拷貝的一種優化吧,意思是隻有當發生寫操做的時候才進行深拷貝
舉個例子:
$a="3333"; $b=$a; $b.="444";
$a="3333";
$b=$a;
這個操做會使兩個zval指向同一個zend_value
這裏並無觸發COW,執行深拷貝
當$b.="444";發生了寫操做的時候,觸發COW,執行深拷貝,拷貝了徹底同樣的一份zend_value,$b所在的zval由原來的和$a所在的zval共同指向以前的zend_value, 轉換成指向拷貝出的新的zend_value
若是不進行深拷貝的話,那麼當執行$b.="444";後,$a也會等於「3333444」
固然不是全部的zend_value類型均可以進行復制,這個請參見我以前的那個zend_uchar type_flags表
**文章中可能有些不足的地方,懇求指正。
原本打算再寫寫GC回收的原理的,可是如今已經2點多了,23333333.明天還要繼續打碼呢。。。。。。。很差意思**
下一篇準備寫一下php中的數組的實現;
lift needs art,i need girl
http://bbs.csdn.net/topics/39...
http://blog.csdn.net/black_ox...
http://blog.csdn.net/xiaolei1...
https://segmentfault.com/a/11...
https://github.com/laruence/p...
https://www.cnblogs.com/ohmyg...
可能有遺漏的參考博客,望博主見諒