php - 一段仅当使用调试类函数才能正常运行的小代码,有人能解释下吗?
PHP中文网
PHP中文网 2017-04-11 09:05:53
[PHP讨论组]

项目里面发现的问题,最简化后如下

$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

PHP中文网
PHP中文网

认证高级PHP讲师

全部回复(2)
PHPz

整理成一篇文章,更准确和详细:SplFixedArray不是“正常”的类

有趣的问题。

一个简化的例子,可以重现类似的现象:

<?php

$arrA = SplFixedArray ::fromArray(array(true));
$arrB = SplFixedArray ::fromArray(array(false));

var_dump($arrA);
var_dump($arrB);

$arrB[0] = true;

//var_dump($arrB);

$equal = ($arrA == $arrB);
var_dump($equal);

注释和不注释的结果:https://3v4l.org/KlODV 和 https://3v4l.org/kfaC0

从php5.3.0到php7.1.0都是同样的错误,但是HHVM里的结果是正确的(看上面的链接)。

进一步简化,也是同样现象:

<?php

$arrA = SplFixedArray ::fromArray(array(true));
$arrB = SplFixedArray ::fromArray(array(false));

$arrB[0] = true;

var_dump($arrB);

$equal = ($arrA == $arrB);
var_dump($equal);

更让我意外的是:

<?php

$arrA = SplFixedArray ::fromArray(array(true));
$arrB = SplFixedArray ::fromArray(array(false));

//var_dump($arrB);

$equal = ($arrA == $arrB);
var_dump($equal);

结果居然是true

然后我调试PHP 7.0.9的代码,发现关键的比较对象的时候是zend_std_compare_objects,其中关键一步是:

if (!zobj1->properties && !zobj2->properties) {
        zval *p1, *p2, *end;

        if (!zobj1->ce->default_properties_count) {
            return 0;
        }
   ...
}  else {
        if (!zobj1->properties) {
            rebuild_object_properties(zobj1);
        }
        if (!zobj2->properties) {
            rebuild_object_properties(zobj2);
        }
        return zend_compare_symbol_tables(zobj1->properties, zobj2->properties);
    }

没有var_dump($arrB)时,zobj1zobj2properties都是空的,进入第一个分支,SplFixedArraydefault_properties_count为0,表示这个对象里没有任何属性,所以就不用对比啦,直接相等返回0。

而有var_dump($arrB)时,zobj2properties不为空,进入了第二个分支,里面会rebuild_object_properties(zobj2)然后再对比。

所以两个问题:

  1. SplFixedArray的类定义(ce)为什么没有默认属性(default_properties_count = 0)

  2. var_dump为什么会让$arrB有了properties

第一点很可能是设计的考虑,我们先看第二点。

看了var_dump的代码发现,会调用spl_fixedarray_object_get_properties,里面调用zend_std_get_properties,就会用rebuild_object_properties创建$arrBpropertiesspl_fixedarray_object_get_properties再把数组内容复制到properties

综合来看,SplFixedArray并不是一个“正常”的类:

  1. 为了表现成一个数组和提高性能,它默认没有属性,不暴露出底层的固定长度的C数组(不是HashTable)

  2. 比较相等的时候按对象的方式处理,先判定是否有属性,而不是直接按数组内容逐个比较

  3. 输出(var_dumpjson_encode……)的时候,会创建属性然后把C数组的内容复制到属性中

结合起来就导致这种奇怪的现象了。

而且,var_dump因为会导致数组复制,SplFixedArray的内存占用会大幅增加:

<?php

$b0 = memory_get_usage(true);

$arr = [];
for ($i = 0; $i < 100000; $i++) {
    $arr[$i] = $i;
}
$b1 = memory_get_usage(true);

$arrB = SplFixedArray ::fromArray($arr);
$b2 = memory_get_usage(true);

json_encode($arrB);
$b3 = memory_get_usage(true);

var_dump($b1 - $b0, $b2 - $b1, $b3 - $b2);

所以用SplFixedArray要注意,本来内存够的,输出的时候瞬间爆炸……

然后为什么HHVM的实现是个正常的对象呢?因为它的SplFixedArray是直接用纯PHP而不是C实现的,直接封装了PHP的数组,提供一个相同的接口而已。

黄舟

$arrB[0] = true;楼主,这样赋值是不对类内部的属性进行修改的,$equal = ($arrA == $arrB)的结果当然还是初始化的时候那个结果啊

热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号