SplFixedArray不是“正常”的类

作者: oraoto | 来源:发表于2017-01-03 18:33 被阅读179次

最近在SegmentFault答了一个关于SplFixedArray的问题,重新整理成本文。

现象

<?php

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

//json_encode($arrB);

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

注释掉json_encode($arrB)时,$equaltrue,去掉注释,$equalfalse

这个现象在PHP 5.3.0 - PHP 7.1.0里都存在。

PHP是如果比较对象相等的?

按直觉,两个SplFixedArray对象里的数组内容是不同的,不应该出现$equaltrue的情况。我们看一下PHP源代码中比较对象相等的代码,我加了点注释:

// Zend/zend_object_handlers.c
// 注意:调用zend_std_compare_objects前已经判定了o1和o2地址不同

static int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */
{
    zend_object *zobj1, *zobj2;

    zobj1 = Z_OBJ_P(o1);
    zobj2 = Z_OBJ_P(o2);

    if (zobj1->ce != zobj2->ce) {  // 如果是不同类的对象,一定不相等
        return 1; /* different classes */
    }

    if (!zobj1->properties && !zobj2->properties) { // Step 1: 如果两个对象没有动态添加属性
        zval *p1, *p2, *end;

        if (!zobj1->ce->default_properties_count) { // Step 2: 如果类定义(Class Entry)里没有定义成员变量
            return 0;  // Step 3: 相等
        }

        // Step 4: 对比类定义的成员变量
        p1 = zobj1->properties_table;
        p2 = zobj2->properties_table;
        end = p1 + zobj1->ce->default_properties_count;
        Z_OBJ_PROTECT_RECURSION(o1);
        Z_OBJ_PROTECT_RECURSION(o2);
        do {
            ...
        } while (p1 != end);
        Z_OBJ_UNPROTECT_RECURSION(o1);
        Z_OBJ_UNPROTECT_RECURSION(o2);
        return 0;
    } else {
        // Step 4:重建properties
        if (!zobj1->properties) {
            rebuild_object_properties(zobj1);
        }
        if (!zobj2->properties) {
            rebuild_object_properties(zobj2);
        }
        // Step 5:对比properties
        return zend_compare_symbol_tables(zobj1->properties, zobj2->properties);
    }
}

ce表示Class Entry,保存类的定义,properties_table是对象的成员变量,properties是对象属性(包括了成员变量),两者是有区别的:

class A {
    public $a;
    public $b = 2;
}

$a1 = new A();
$a2 = new A();
$a2->a = 1;
$a2->c = 2;  // 添加了c

执行完上面代码后,$a1$a2的properties_table都有两个元素(a, b),$a1properties是空的,而$a2的是有3元素的。
即动态添加属性时,会把properties_table的成员变量到properties里,然后在添加到properties

根据上面的代码,总结对象的比较规则:

  1. 如果两个对象是不同类型,不相等
  2. 如果两个对象都没有动态添加属性(properties为空),比较两者的成员变量(properties_table)
  3. 如果其中一个对象有动态添加属性(properties不为空),如果另一个没有的则添加(rebuild_object_properties会复制properties_table),然后比较两者的属性(properties

回到第一部分SplFixedArray的测试代码,调试时发现,没有json_encode($arrB)时,$arrBproperties是空的,表示没有动态添加属性,而SplFixedArray类也没定义成员变量,
所以走代码中的Step 1 -> Step 2 -> Step 3,直接返回0表示相等。
而调用了json_encode($arrB)之后,$arrBproperties就不为空了,比较流程就变成:Step 1 -> Step 4 -> Step 5,这个时候就会比较对象的属性。

到这里,我们可以确定:

  1. SplFixedArray对象本来是没有成员变量、没有动态添加的属性,==比较都返回true
  2. SplFixedArray对象在json_encode后有了动态添加的属性,==比较对象的属性

json_encode为什么会动态添加属性?

json_encode的代码,其中是这一句:myht = Z_OBJPROP_P(val)Z_OBJPROP_P的定义:

#define Z_OBJPROP_P(zval_p) Z_OBJPROP(*(zval_p))

#define Z_OBJDEBUG(zval,tmp) (Z_OBJ_HANDLER((zval),get_debug_info)?Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&tmp):(tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL))

简单来说就是调用对象的object handler里的里的get_debug_info或者get_propertiesSplFixedArrayget_properties是这样的:

static HashTable* spl_fixedarray_object_get_properties(zval *obj) /* {{{{ */
{
    spl_fixedarray_object *intern  = Z_SPLFIXEDARRAY_P(obj);
    HashTable *ht = zend_std_get_properties(obj);
    zend_long  i = 0;

    if (intern->array) {
        ... 复制数组到ht
    }

    return ht;
}

其中调用了zend_std_get_properties

ZEND_API HashTable *zend_std_get_properties(zval *object) /* {{{ */
{
    zend_object *zobj;
    zobj = Z_OBJ_P(object);
    if (!zobj->properties) {
        rebuild_object_properties(zobj);
    }
    return zobj->properties;
}

其中又调用了rebuild_object_properties,创建了properties

类似的,var_dump也会又类似的获取和创建对象属性的流程。

结果

  1. SplFixedArray对象不能通过==进行比较
  2. 使用get_properties的函数(var_dumpjson_encode……)会导致SplFixedArray复制底层的C数组到PHP的数组,导致内存占用增大

可能的修复方式

  1. SplFixedArrayobject handler要定义compare_objects,实现正确的比较
  2. SplFixedArrayget_properties不要调用zend_std_get_properties,而是直接返回一个HashTable,之后让gc清理掉,避免一直占用内存。

但是,还没测试过,不知实际可不可行。

相关文章

  • SplFixedArray不是“正常”的类

    最近在SegmentFault答了一个关于SplFixedArray的问题,重新整理成本文。 现象 注释掉json...

  • SPL

    栈 SplStack 队列 SplQueue 堆 SplHeap 定长数组 SplFixedArray

  • spl标准库之splFixedArray

    splFixedArray是PHP官方提供的SPL标准库中提供的其中一个数据接口,不同于PHP的array,他更偏...

  • PHP SPL标准库之固定大小数组

    简介 SplFixedArray,字面意思就是固定大小的数组,意思就是在定义的时候就要确定大小,有点C语言基础的人...

  • PHP的另一个数组SplFixedArray

    SplFixedArray主要是处理数组相关的主要功能,与普通php array不同的是,它是固定长度的,且以数字...

  • 产品体验:SNOW

    1、上来就要登录,如果是社交类等明显有这方面需要的产品,是很正常的。但是,作为摄影录像类,并不是,不管你是不是将社...

  • java程序启动,类的加载情况

    一、java程序运行的时候,不是所有类必须被加载到jvm虚拟机中才可以正常使用 是不是所有的class文件都在启动...

  • 不是正常人!!?

    灵动的双眸,高挺的鼻梁,樱桃般的小嘴,白嫩的肌肤,乌黑亮丽的长发,细长的美腿,典型的青春美少女啊!可是...

  • 显式依赖关系

    显式依赖关系 方法和类应显式要求正常工作所需的任何协作对象。 通过类构造函数,类可以标识其实现有效状态和正常工作所...

  • java——异常

    异常:是在运行时期发生的不正常情况 在java中用类的形式对不正常情况进行了描述和封装对象。描述不正常的情况的类,...

网友评论

    本文标题:SplFixedArray不是“正常”的类

    本文链接:https://www.haomeiwen.com/subject/seenvttx.html