🌚

PHP对象的实现和操作

Posted at — Apr 29, 2015
#PHP #源码

Object的存储结构

对象实例用zval存储。zval->type == IS_OBJECT,zval->value->obj存储zend_object_value类型的结构体变量。

1
2
3
4
typedef struct _zend_object_value {
    zend_object_handle handle;
    const zend_object_handlers *handlers;
} zend_object_value;

zend_object_handle是一个unsigned int,是对象的ID。zend_object_handlers存储对象所有的行为。

Object的实例化过程

Object的初始化用以下几个宏函数:

底层都是调用_object_and_properties_init(zval *arg, zend_class_entry *class_type, HashTable *properties)实现。这个函数做以下几件事:

zend_objects_new()做这些事:

zend_object的存储结构

1
2
3
4
5
6
typedef struct _zend_object {
    zend_class_entry *ce;
    HashTable *properties;
    zval **properties_table;
    HashTable *guards; /* protects from __get/__set ... recursion */
} zend_object;

ce是类的定义。properties_table存储类里预定义的属性。properties存储非预定义属性。

guards存储属性名到zend_guard结构的映射关系。

1
2
3
4
5
6
7
typedef struct _zend_guard {
    zend_bool in_get;
    zend_bool in_set;
    zend_bool in_unset;
    zend_bool in_isset;
    zend_bool dummy; /* sizeof(zend_guard) must not be equal to sizeof(void*) */
} zend_guard;

此结构用于在操作属性时,防止递归调用。例如给对象一个新属性赋值时,__set()函数理论上会递归调用自己,所以此结构用于判断该属性是否已在__set()中。

属性的存储结构

在zend_object的存储结构里,哈希表properties存储类的非预定义属性的名称和值。

对于预定义的属性,由于PHP的哈希表的存储开销很大,所以把属性信息(即下面的zend_property_info结构体)存储在zend_class_entry里,对象里用C的数组存储所有预定义属性的zval的指针,并把偏移量记录在属性信息里,这就是properties_table。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
typedef struct _zend_property_info {
    zend_uint flags;
    const char *name;
    int name_length;
    ulong h;                 /* hash of name */
    int offset;              /* storage offset */
    const char *doc_comment;
    int doc_comment_len;
    zend_class_entry *ce;    /* CE of declaring class */
} zend_property_info;

属性名的编码

在类的继承关系中,同名不同类型(public,private等)的属性各自单独存储,所以属性名在底层是经过编码的,规则如下:

class Foo { private $prop; } => “\0Foo\0prop”
class Bar { private $prop; } => “\0Bar\0prop”
class Rab { protected $prop; } => “\0*\0prop”
class Oof { public $prop; } => “prop”

大部分情况下,对属性操作的API自动处理属性名的编码。只有当需要直接访问property_info->name或zobj->properties时才需要自行处理,此时使用zend_(un)mangle_property_name()函数。

Objects Store的存储结构

对象仓库是一个可变数组,存储多个zend_object_store_bucket结构。

1
2
3
4
5
6
typedef struct _zend_objects_store {
    zend_object_store_bucket *object_buckets;
    zend_uint top;
    zend_uint size;
    int free_list_head;
} zend_objects_store;

size是对象仓库的容量。top是下一个可用的对象句柄,对象句柄从1开始,以保证所有句柄都为真。对象仓库通过每个Bucket的free_list结构维护一个可用的Bucket链表,free_list_head记录链表的头部。

zend_object_store_bucket的存储结构

每个对象的信息存储在一个bucket里。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
typedef struct _zend_object_store_bucket {
    zend_bool destructor_called;
    zend_bool valid;
    union _store_bucket {
        struct _store_object {
            void *object;
            zend_objects_store_dtor_t dtor;
            zend_objects_free_object_storage_t free_storage;
            zend_objects_store_clone_t clone;
            const zend_object_handlers *handlers;
            zend_uint refcount;
            gc_root_buffer *buffered;
        } obj;
        struct {
            int next;
        } free_list;
    } bucket;
} zend_object_store_bucket;

桶被占用的时候,valid为1,否则为0。

对象被销毁时,dtor被调用后,destructor_called被置为1,防止在被free时重复调用dtor,具体见Object的二阶销毁逻辑

_store_object里存储对象的主要信息。zend_objects_store_put()传入的zend_object结构体存储在object里。dtor和free_storage见Object的二阶销毁逻辑。clone是对象的克隆函数。handlers存储对象的一系列操作函数,缺省为std_object_handlers。refcount是对象的引用计数。buffered是垃圾回收需要用到的数据。

free_list记录对象仓库中可用的Bucket链表中下一个可用的Bucket。

Object Store的操作

Object的二阶销毁逻辑

对象的销毁分两个步骤,一是对象的析构,一是内存的释放。前者调用对象的dtor,后者调用free_storage。一般先析构,再释放内存,但两者可各自分开执行。

dtor中可以执行用户空间的PHP代码,主要是PHP类的__destruct()。PHP脚本执行完成后销毁对象并结束进程(executor shutdown),在这个过程进行到一半的时候执行用户空间代码可能会出问题,所以这么区别主要是为了在进程结束过程中不会调用用户空间代码。

此外,dtor并不是必须执行的,如果一个对象的dtor调用的用户空间代码里执行了die(),后续对象的dtor不会被执行。所以大部分情况下,开发者可以自定义free_storage函数,而使用缺省的zend_objects_destroy_object作为dtor。