php7变量实现_class+object

class实现

//Zend/zend.h
struct _zend_class_entry {
    char type;                                      //类的类型,ZEND_INTERNAL_CLASS(=1)标志内置类,ZEND_USER_CLASS(=2)标志自定义类
    zend_string *name;                              //类名
    /* class_entry or string depending on ZEND_ACC_LINKED */
    union {                                         //父类
        zend_class_entry *parent;
        zend_string *parent_name;
    };
    int refcount;                                   //引用计数
    uint32_t ce_flags;                              //位组合标记

    int default_properties_count;                   //默认普通属性数量
    int default_static_members_count;               //默认静态属性数量
    zval *default_properties_table;                 //默认普通属性值数组
    zval *default_static_members_table;             //静态属性值数组
    ZEND_MAP_PTR_DEF(zval *, static_members_table); //静态属性成员数组,这个宏在zend_map_ptr.h中,替换后为zval ** static_members_table__ptr
    HashTable function_table;                       //成员方法哈希表
    HashTable properties_info;                      //成员变量哈希表
    HashTable constants_table;                      //常量哈希表

    struct _zend_property_info **properties_info_table;

    zend_function *constructor;
    zend_function *destructor;
    zend_function *clone;
    zend_function *__get;
    zend_function *__set;
    zend_function *__unset;
    zend_function *__isset;
    zend_function *__call;
    zend_function *__callstatic;
    zend_function *__tostring;
    zend_function *__debugInfo;
    zend_function *serialize_func;              //对象序列化方法
    zend_function *unserialize_func;            //对象反序列化方法

    /* allocated only if class implements Iterator or IteratorAggregate interface */
    zend_class_iterator_funcs *iterator_funcs_ptr;

    /* handlers */
    union {
        zend_object* (*create_object)(zend_class_entry *class_type);    //实例化该类时调用的方法,默认为zend_objects_new
        int (*interface_gets_implemented)(zend_class_entry *iface, zend_class_entry *class_type); /* a class implements this interface */
    };
    zend_object_iterator *(*get_iterator)(zend_class_entry *ce, zval *object, int by_ref);
    zend_function *(*get_static_method)(zend_class_entry *ce, zend_string* method);

    /* serializer callbacks */
    int (*serialize)(zval *object, unsigned char **buffer, size_t *buf_len, zend_serialize_data *data);
    int (*unserialize)(zval *object, zend_class_entry *ce, const unsigned char *buf, size_t buf_len, zend_unserialize_data *data);

    uint32_t num_interfaces;                    //类的接口数量
    uint32_t num_traits;                        //类的特性数量

    /* class_entry or string(s) depending on ZEND_ACC_LINKED */
    union {
        zend_class_entry **interfaces;
        zend_class_name *interface_names;
    };

    zend_class_name *trait_names;               //特性指针
    zend_trait_alias **trait_aliases;           //特性别名数组
    zend_trait_precedence **trait_precedences;  //特性优先级数组

    union {
        struct {
            zend_string *filename;
            uint32_t line_start;
            uint32_t line_end;
            zend_string *doc_comment;
        } user;
        struct {
            const struct _zend_function_entry *builtin_functions;
            struct _zend_module_entry *module;      //所属拓展
        } internal;
    } info;
};

ce_flags位组合标记对类进行一些标记,不同的位有不同的标记,如是否含有抽象方法,是否为抽象类,是否为接口,是否为特性等,具体的可以在Zend/zend_compile.h文件中找到。

在function_table和properties_info中保存的值是zend_property_info

typedef struct _zend_property_info { 
    uint32_t offset; /* property offset for object properties or
                          property index for static properties */
    uint32_t flags;         //位标志,是否为静态属性记权限控制
    zend_string *name;      //经过处理的属性名,比如私有属性前加上类名。处理方法同序列化时的处理方法
    zend_string *doc_comment;//函数注释
    zend_class_entry *ce;   //所属类的指针
    zend_type type;
} zend_property_info;

哈希表表中的元素为bucket,bucket中有一个zval类型的val,val.value.ptr指向zend_property_info。zval中的ptr指针是void类型的,可以执行任何类型的数据。

flags,属性权限即是否静态标志

#define ZEND_ACC_PUBLIC                  (1 <<  0) 
#define ZEND_ACC_PROTECTED               (1 <<  1) 
#define ZEND_ACC_PRIVATE                 (1 <<  2) 
#define ZEND_ACC_STATIC                  (1 <<  4) 

offset,这个值有两个含义,对于静态变量它是索引,对于普通属性它是偏移量。通过这个索引/偏移量可以在相应的数组中找到值。这里可能不好理解,看下面object代码就明白了

struct _zend_object {
    zend_refcounted_h gc;   
    uint32_t          handle; // TODO: may be removed ???//该object在全局全局对象符号表中的索引
    zend_class_entry *ce;       //object所属class指针           
    const zend_object_handlers *handlers;   //初始化时默认指向全局变量std_object_habdlers,包括操作对象属性等的多个指针函数
    HashTable        *properties;           //动态普通属性
    zval              properties_table[1];  //普通属性值组
};

在object中并没有储存属性名到属性值之间的映射,那么怎么访问属性呢。实际上访问object属性时会首先到object.ce.properties_info中查找相应key对应zend_property_info中的offset。对于静态属性这是一个索引值,通过object.ce.default_static_members_table[offset],就能获取到属性值,对于普通属性这是一个相对于当前object的偏移量,普通属性保存在object.properties_table中,这是一个柔性数组,通过这个偏移量就能获取到普通属性,普通属性的offset=40,56,72...,因为object大小为40字节,所以object基址+40就是properties_table中第一个元素的地址,而每个zval16字节,依次增加16就能访问到后续元素。

通过这种方式object中只需要一个zval数组就能保存所有的普通属性值。如果按照直观的想法,在object中使用HashTable保存属性名到属性值之间的映射,那么每一个普通属性需要一个bucket,n个属性就至少需要n个bucket+n个slot,bucket32字节,slot4字节,共36字节,bucket中的key指针指向一个zend_string,32字节,因此每个普通属性需要68字节内存。而通过代码中的这种方式,只需要一个zval数组,zval16字节。那么每个对象的每个属性都能节省52个字节内存。虽然因此导致class结构内存增加,但是类的数量远小于对象数量,采用这种方式可以减少很多内存开销。

还有一个动态属性组,这个HashTable保存的是运行时动态创建的属性,如

<?php
    class user_class1{
    public $var1;
    public function __construct(){
        $this->var2 = 1;
    }
}

这里的var2就是一个运行时创建的动态属性。由于类是在编译阶段创建的,这时还没有创建动态属性var2,因此该类的属性表中没有这个属性,那么就不能通过和普通属性一样的方式保存。object用使用了一个单独的properties来保存动态属性。

在class中还有一个属性ZEND_MAP_PTR_DEF(zval *, static_members_table);这个属性经过宏替换后称为zval ** static_members_table__ptr

//zend_compile.c
    if (ce->type == ZEND_INTERNAL_CLASS) {
        ZEND_MAP_PTR_INIT(ce->static_members_table, NULL);
    } else {
        ZEND_MAP_PTR_INIT(ce->static_members_table, &ce->default_static_members_table);
        ce->info.user.doc_comment = NULL;
    }


    if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) {
        ce->ce_flags |= ZEND_ACC_PRELOADED;
        ZEND_MAP_PTR_NEW(ce->static_members_table);
    }

初始化的时候只有这两个地方出现了这个字段,看其他文章里说是给自定义class调用的,但是感觉和default_static_members_table没什么区别,不知道有什么用。

class中default_properties_table保存的是普通属性的默认值也就是类定义时给出的值。

还有一个问题就是对于静态变量,也和普通属性一样保存在properties_info中,然后取得zend_property_info中的offset,再根据这个索引在default_static_members_table中取得值。但是static作为类变量,可以直接在class中用一个HashTable保存变量名到值得映射,不需要像普通属性一样保存方式,这样不光更节省内存,也减少了时间开销。不知道为什么会和普通属性用一样得保存方式。

zend_class_entry结构体很大但zend_object结构体很小,因为大部分数据都保存在class中,这样可以减少内存开销(class很少但object很多)。保存在object得数据主要有该对象得普通属性值数组和动态属性映射表,其余的内容包括普通属性名,普通属性映射关系,静态属性,方法等都保存在class中。class中不保存属性值,因为属性是object的,当存在继承关系时,可能会出现变量名冲突,由于properties_table是一个数组,那么对于同名属性只会保存一个,其他同名值在大部分时候并不会被删除,而是放在object的柔性数组properties中,通过控制properties_table中zend_property_info中的offset就能控制该属性名对应哪个属性值。

  • 父类属性不与子类冲突 且 父类属性是私有: 即父类属性为private,且子类中没有重名的,则将此属性插入子类properties_info,但是更新其flag为ZEND_ACC_SHADOW,这种属性将不能被子类使用;
  • 父类属性不与子类冲突 且 父类属性是公有: 这种比较简单,子类可以继承使用,直接插入子类properties_info;
  • 父类属性与子类冲突 且 父类属性为私有: 不继承父类的,以子类原属性为准,但是打上ZEND_ACC_CHANGED的flag,这种属性父子类隔离,互不干扰;
  • 父类属性与子类冲突 且 父类属性是公有或受保护的:
    • 父子类属性一个是静态一个是非静态: 编译错误;
    • 父子类属性都是非静态: 用父类的offset,但是值用子类的,父子类共享;
    • 父子类属性都是静态: 不继承父类属性,以子类原属性为准,父子类隔离,互不干扰;

由于对象的方法都保存在数组里,因此访问对象的方法时会进入class中找到相应的函数执行,同时要传入$this为参数执行当前对象。因为在class中,所有的方法都保存在function_table中,那么也可以通过访问静态方法的方式访问普通方法(class::func()),由于这种调用方式是直接在类上调用的,因此不会传入$this参数,如果在调用的函数里没有访问$this变量,那么能执行成功,否则会因为找不到$this而报错。关于方法调用,《PHP7底层涉及与源码实现》P151给出了一个很有趣C++代码,如下

#include<iostream>
using namespace std;
class Php{
    protected:
        std::string _version;
        public:
        void version(){
            std::cout << "7.1.0" << endl;
            this->_version = "7.1.0";
        }
};

int main(int argc, char * argv[]){
    Php * php = (Php *)0;
    php->version();
    return 0;
}

这段代码会执行到this->_version = "7.1.0";一行然后报错。这里给php赋得地址是0,这个地址是操作系统得保留地址,用户态程序是没有权限访问的,但是对version方法的调用仍然成功了,因为方法保存在class中而不是object中,因此访问Php类型的php对象的方法时,直接在全局符号表EG(class_table)找到Php类的结构,并且将$this(即(Php *)0)传入调用version方法。在version方法内访问了this->_version,但this这个对象无法访问所以会在那一行报错。

对于class的继承,是在编译完成后进行的,父类和字类分开编译。所谓继承也就是将父类的属性、方法拷贝给字类,以及出现冲突时的处理。

对于普通属性,先申请一个大小为父子属性数之和的table数组,然后将父类default_properties_table放在前,之类的放在后,然后释放字类的default_properties_table并将table赋给该值。对于静态属性操作类似。修改完值数组后,还需要修改几个HashTable,即属性的索引。由于前一步合并属性值时父属性在前,子属性在后,因此对于静态属性子类properties_table值中zend_property_info中的offset需要+parent_ce->default_static_members_count,对于普通属性,offset+=parent_properties_count*sizeof(zval),然后以和default_properties_table相同的顺序合并properties_info。对冲突属性的处理在上面已经给出了。

对于常量,直接合并常量HashTable,冲突的用字类覆盖父类。

对于方法也类似,不过加上了对final、abstract等方法的判定和处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268