變量
PHP5 變量的內部實現
變量存儲結構:
typedef struct _zval_struct zval;
...
struct _zval_struct {
/* Variable information */
zvalue_value value; /*變量的值的存放容器*/
zend_uint refcount__gc; /*引用計數器*/
zend_uchar type; /*變量類型*/
zend_uchar is_ref__gc; /*變量是否爲引用變量*/
};
變量的值存儲結構:(union能節省內存空間,因爲所有的字段重疊在內存中的相同偏移處,且一個變量同時只能屬於一種類型)
typedef union _zvalue_value {
long lval; /* integer */
double dval; /* float */
struct { /* string */
char *val;
int len;
} str;
HashTable *ht; /* hash table */
zend_object_value obj; /* obj */
} zvalue_value;
/*另外還有 null類型 和 resource類型*/
PHP7 變量的內部實現
變量存儲結構:
typedef struct _zval_struct zval;
struct _zval_struct {
zend_value value; //變量實際的value
union {
struct {
ZEND_ENDIAN_LOHI_4(
zend_uchar type, //變量類型
zend_uchar type_flags, //類型掩碼
zend_uchar const_flags,
zend_uchar reserved) //call info,zend執行流程會用到
} v;
uint32_t type_info; //上面4個值的組合值,可以直接根據type_info取到4個對應位置的值
} u1;
union {
uint32_t var_flags;
uint32_t next; //哈希表中解決哈希衝突時用到
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; //一些輔助值
};
變量的值存儲結構:
typedef union _zend_value {
zend_long lval; //int整形
double dval; //浮點型
zend_refcounted *counted;
zend_string *str; //string字符串
zend_array *arr; //array數組
zend_object *obj; //object對象
zend_resource *res; //resource資源類型
zend_reference *ref; //引用類型,通過&$var_name定義的
zend_ast_ref *ast; //下面幾個都是內核使用的value
zval *zv;
void *ptr;
zend_class_entry *ce;
zend_function *func;
struct {
uint32_t w1;
uint32_t w2;
} ww;
} zend_value;
二者的區別:
1、在PHP5中,變量引用計數器是存在變量容器zval中的,且用is_ref判斷變量是否爲引用類型,然而在PHP7中,變量計數器不再存放在zval中,且也不再通過is_ref判斷變量是否爲引用類型,而刪除了zval中的is_count和is_ref標識字段而在zend_value增加了一個zend_referrence類型 ,而將引用計數存新增到了每個類型字段中。
2、引用類型的差別:
PHP5是通過is_ref字段來區分一個變量是否爲引用類型;
而PHP7的引用原理如下:
數據結構:
struct _zend_reference {
zend_refcounted_h gc;
zval val;
};
引用是PHP中比較特殊的一種類型,它實際是指向另外一個PHP變量,對它的修改會直接改動實際指向的zval,可以簡單的理解爲C中的指針,在PHP中通過&操作符產生一個引用變量,也就是說不管以前的類型是什麼,&首先會創建一個zend_reference結構,其內嵌了一個zval,這個zval的value指向原來zval的value(如果是布爾、整形、浮點則直接複製原來的值),然後將原zval的類型修改爲IS_REFERENCE,原zval的value指向新創建的zend_reference結構。最終zval的value的值存儲在zend_reference這個結構的zval字段的value中,而新老zval的value都指向了zend_reference且zval的類型(type字段定義)爲ref。
<?php
//注意:引用只能通過&產生,無法通過賦值傳遞,比如:
$a = "time:" . time(); //$a -> zend_string_1(refcount=1)
$b = &$a; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = $b; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=2)
//$c -> ---
//$b = &$a這時候$a、$b的類型是引用,但是$c = $b並不會直接將$b賦值給$c,而是把$b實際指向的zval的value賦值給$c,如果想要$c也是一個引用則需要這麼操作:
$a = "time:" . time(); //$a -> zend_string_1(refcount=1)
$b = &$a; //$a,$b -> zend_reference_1(refcount=2) -> zend_string_1(refcount=1)
$c = &$b;/*或$c = &$a*/ //$a,$b,$c -> zend_reference_1(refcount=3) -> zend_string_1(refcount=1)
*注:這個也表示PHP中的 引用只可能有一層 ,不會出現一個引用指向另外一個引用的情況 ,也就是沒有C語言中指針的指針的概念。
引用:(引用 != 指針)
php引入引用的目的:
背景:
【
其主要目的是遵循『面對對象模式』:對象傳參給函數或者方法後,這個函數發送一個指令給對象(例如調用了一個方法)以此來改變對象的狀態(例如對象的屬性)。因此傳參進去的對象必須爲同一個。
】
實際:
【
在 PHP 4 中,對象被當成變量來對待,所以當對象作爲函數傳參時,他們是被複制的。 PHP 4 的面對對象用戶使用『引用傳參』來解決這個問題,不過很難做到完美。
PHP 5 引進了獨立於變量容器的『對象存儲器』。當一個對象賦值給變量時,變量不再存儲整個對象(屬性表和其他的『類』信息),而是存儲這個對象所在 存儲器的引用 —— 當我們複製一個對象變量時,我們複製的是這個『存儲器的引用』。
】
*注:【這很容易被誤解爲『引用』,但是『存儲器的引用』與『引用』是完全不同的概念。】
php引用的本質:
宏觀來看:引用是變量的別名
從php5和php7分別來看:
【
php5中通過zval結構的is_ref字段表示該變量是否爲引用變量,故再使用引用類型變量進行賦值操作時,會有不同的賦值操作(正常賦值或者變量分離等)
php7新增了引用類型變量值容器`struct _zend_reference`,同樣對引用類型變量進行賦值操作時,會有不同的賦值操作(引用變量的值替換`作爲左值`或者變量分離`作爲右值`等)
】
對一個變量進行賦值時,會遞歸遍歷右值的所有值類型嗎(然後對是引用類型的變量發生`變量分離操作`)?
<?php
$a['hello'] = 'world';
$a['l'] = 1;
$b = &$a['hello'];
$c = $a;
$c['hello'] = 'kaka';
$c['l'] = 2;
/**
* 引用可以將一個變量給污染了
* 原因如下:
* $a['hello']由於`被引用`,因此$a['hello']成了一個引用類型的變量,然而$c = $a的賦值操作中,由於$a並不是引用類型的變量,所以$c和$a指向同一個`變量值容器`, 當執行 $c['hello'] = 'kaka' 這個賦值操作時,由於$c['hello']是一個引用類型,會直接在更改值內容,而不會發生`寫時複製`,而$c['l']變量類型不爲引用類型,且$c['l']的值不等於原值,所以會發生一次`寫時複製`操作(即產生一塊新的內存,將新值賦值給該內存,然後$c['1']重新指向這塊新內存)
*/
var_dump($a);
var_dump($c);
答案是:不會!!
寫時複製
如果通過賦值的方式賦值給變量時不一定會申請新內存來存放 新變量的值,而是簡單的通過一個`變量值容器`來共用內存,只有在其中的一個引用這個`容器`的變量的 值發生變化時才申請新空間來保存值內容以減少對內存的佔用。 在很多場景下PHP都COW進行內存的優化。比如:變量的多次賦值、函數參數傳遞,並在函數體內修改實參等。
變量分離
<?php
$a = 1;
$b = $a;
$a = 2;
當$foo的值被賦給$bar時,PHP並沒有將內存複製一份交給$bar,而是把$foo和$bar指向同一個地址。 隨後,我們更改了$bar的值,這時如果直接需該$bar變量指向的內存,則$foo的值也會跟着改變。 這不是我們想要的結果。於是,PHP內核將內存複製出來一份,並將其值更新爲賦值的:2(這個操作也稱爲變量分離操作), 同時原$foo變量指向的內存只有$foo指向。所以變量分離是寫時複製時產生的一種現象(自己的解釋)。
參考:
http://www.php-internals.com/book/?p=chapt06/06-06-copy-on-write 《寫時複製》
https://zhuanlan.zhihu.com/p/35107602 《PHP 引用是個坑,請慎用》
https://github.com/pangudashu/php7-internal 《php7-內核分析》