zval介紹
先看如下代碼
<?php
$arr = [1, 2, 3];
foreach ($arr as &$item) {
var_dump($item);
}
echo "\n\n";
foreach ($arr as $item) {
var_dump($item);
}
預期結果應該是
int(1)
int(2)
int(3)
int(1)
int(2)
int(3)
[Finished in 0.1s]
運行結果卻是
int(1)
int(2)
int(3)
int(1)
int(2)
int(2)
[Finished in 0.1s]
代碼改爲
<?php
$arr = [1, 2, 3];
foreach ($arr as &$item) {
var_dump($item);
}
unset($item);
echo "\n\n";
foreach ($arr as $item) {
var_dump($item);
}
結果就正常了
but why???
說原因之前,先了解下ZVAL的大致結構:
“zval結構體中有四個字段,其含義分別爲:
屬性名 含義 默認值
refcount__gc 表示引用計數 1
is_ref__gc 表示是否爲引用 0
value 存儲變量的值
type 變量具體的類型”
摘錄來自: Reeze Xia. “TIPI: 深入理解PHP內核”。
利用xdebug擴展,可以看到refcount和is_ref的情況:
<?php
$a = "hello world";
xdebug_debug_zval('a');
然後在PHP7下運行:
a: (refcount=2, is_ref=0)='hello world'
在PHP5.5下運行:(我沒試過,因爲環境都是php7)
a: (refcount=1, is_ref=0)='hello world'
爲什麼7的refcount 爲2呢?這裏是php7的字面量也計算了一次次數
<?php
$a = "hello world";
$b = $a . $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
結果爲:
a: (refcount=2, is_ref=0)='hello world'
b: (refcount=1, is_ref=0)='hello worldhello world'
加上引用符號的時候:
<?php
$a = "hello world";
$b = &$a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
結果爲:
a: (refcount=2, is_ref=1)='hello world'
b: (refcount=2, is_ref=1)='hello world'
foreach 內部的實現類似於:
foreach具體實現
foreach ($arr as $k => $item)
$item = $arr[$k];
foreach ($arr as $k => &$item)
$item = &$arr[$k];
瞭解這些之後,回頭看下最初的問題
第一次foreach結束的時候, item並沒有釋放掉,item爲arr[k]的引用
所以在第二次foreach的時候,item始終爲數組最後一個元素的引用,循環的每次,都給數組最後一項進行了賦值操作
所以在倒數第二次的循環的時候,給最後一項賦值了倒數第二項的值。進行最後一次循環的時候,值爲倒數第二項的值,最後一項的值已經丟掉了
在第二次進行循環的時候,給item進行一次unset操作,因爲item是對arr[k]的引用,查看zval如下
item:(refcount=2, is_ref=1)int 1
item:(refcount=2, is_ref=1)int 2
item:(refcount=2, is_ref=1)int 3
進行unset操作只是對應的變量的refcount減1,並不會刪除變量內容
所以第二次的結果就正常了
php的引用計數是對於內存進行的優化,給變量賦值操作的話,不進行拼接等操作,只是引用計數增加,並不會增加內存空間的佔用
函數參數和返回值
對於只是內存操作的話,比如拼裝where條件等(php7.1之後,支持類型限制)
function test_mem(array $arr) : array
{
foreach ( $arr as $item ) {
var_dump($item);
}
return $arr;
}
如果有io操作,比如數據庫操作,http調用操作(進一步可以使用try catch)
函數的返回值標識執行結果的成功與失敗,函數的返回參數通過傳進去一個空的變量,通過這個變量拿出來
function test_io(array &$resp, array $params) : bool
{
$resp = [];
foreach ( $params as $item ) {
var_dump($item);
}
return true;
}
php原生函數,ksort函數的參數就是引用傳值
php也有類似go的返回類型
<?php
function test_go(array $params) : array
{
$ret = [
[], // data
'', // msg
false, // status
];
foreach ( $params as $item ) {
var_dump($item);
}
$ret['data'] = $params;
$ret['msg'] = 'success';
$ret['status'] = true;
return $ret;
}
$data = [];
$msg = '';
$status = false;
$params = [1,2,3,4,5,6];
[$data, $msg, $status] = test_go($params);
進一步延申:
深入理解PHP7內核之zval
比如這種
struct _zval_struct {
union {
long lval;
double dval;
struct {
char *val;
int len;
} str;
HashTable *ht;
zend_object_value obj;
zend_ast *ast;
} value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
};