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;
};