项目里面发现的问题,最简化后如下
$arrA=$arrB=[true,false,true];
$arrA=SplFixedArray ::fromArray($arrA);
$arrB=SplFixedArray ::fromArray($arrB);
var_dump($arrA);
echo '<br>';
var_dump($arrB);
$arrB[2]=false;
echo '<br>';
//使用下面任意语句或者使用xdebug单步执行尝试调试都能获得$equal===false,否则为$equal===true
//var_dump($arrB);
//print_r($arrB);
//json_encode($arrB);
//var_export($arrB);
echo '<br>';
$equal=($arrA==$arrB);
var_dump($equal);
显而易见的$arrB与$arrA是属性是不完全一致的,
所以==的值理应是预期值false,但实际上最终的结果却是true
我尝试去定位这个问题,但是当我尝试使用调试函数如var_export()后,我又得到了预期值false,同样的xdebug单步运行后同样是得到的预期值false
所以这是为什么呢?
运行环境PHP5.7,PHP7.04
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号
整理成一篇文章,更准确和详细:SplFixedArray不是“正常”的类
有趣的问题。
一个简化的例子,可以重现类似的现象:
注释和不注释的结果:https://3v4l.org/KlODV 和 https://3v4l.org/kfaC0
从php5.3.0到php7.1.0都是同样的错误,但是HHVM里的结果是正确的(看上面的链接)。
进一步简化,也是同样现象:
更让我意外的是:
结果居然是
true。然后我调试PHP 7.0.9的代码,发现关键的比较对象的时候是
zend_std_compare_objects,其中关键一步是:没有
var_dump($arrB)时,zobj1和zobj2的properties都是空的,进入第一个分支,SplFixedArray的default_properties_count为0,表示这个对象里没有任何属性,所以就不用对比啦,直接相等返回0。而有
var_dump($arrB)时,zobj2的properties不为空,进入了第二个分支,里面会rebuild_object_properties(zobj2)然后再对比。所以两个问题:
SplFixedArray的类定义(ce)为什么没有默认属性(default_properties_count = 0)var_dump为什么会让$arrB有了properties。第一点很可能是设计的考虑,我们先看第二点。
看了
var_dump的代码发现,会调用spl_fixedarray_object_get_properties,里面调用zend_std_get_properties,就会用rebuild_object_properties创建$arrB的properties,spl_fixedarray_object_get_properties再把数组内容复制到properties。综合来看,
SplFixedArray并不是一个“正常”的类:为了表现成一个数组和提高性能,它默认没有属性,不暴露出底层的固定长度的C数组(不是HashTable)
比较相等的时候按对象的方式处理,先判定是否有属性,而不是直接按数组内容逐个比较
输出(
var_dump、json_encode……)的时候,会创建属性然后把C数组的内容复制到属性中结合起来就导致这种奇怪的现象了。
而且,
var_dump因为会导致数组复制,SplFixedArray的内存占用会大幅增加:所以用SplFixedArray要注意,本来内存够的,输出的时候瞬间爆炸……
然后为什么HHVM的实现是个正常的对象呢?因为它的SplFixedArray是直接用纯PHP而不是C实现的,直接封装了PHP的数组,提供一个相同的接口而已。
$arrB[0] = true;楼主,这样赋值是不对类内部的属性进行修改的,$equal = ($arrA == $arrB)的结果当然还是初始化的时候那个结果啊