php7變量實(shí)現(xiàn)_class+object

class實(shí)現(xiàn)

//Zend/zend.h
struct _zend_class_entry {
    char type;                                      //類的類型,ZEND_INTERNAL_CLASS(=1)標(biāo)志內(nèi)置類苔货,ZEND_USER_CLASS(=2)標(biāo)志自定義類
    zend_string *name;                              //類名
    /* class_entry or string depending on ZEND_ACC_LINKED */
    union {                                         //父類
        zend_class_entry *parent;
        zend_string *parent_name;
    };
    int refcount;                                   //引用計(jì)數(shù)
    uint32_t ce_flags;                              //位組合標(biāo)記

    int default_properties_count;                   //默認(rèn)普通屬性數(shù)量
    int default_static_members_count;               //默認(rèn)靜態(tài)屬性數(shù)量
    zval *default_properties_table;                 //默認(rèn)普通屬性值數(shù)組
    zval *default_static_members_table;             //靜態(tài)屬性值數(shù)組
    ZEND_MAP_PTR_DEF(zval *, static_members_table); //靜態(tài)屬性成員數(shù)組妈候,這個(gè)宏在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;              //對(duì)象序列化方法
    zend_function *unserialize_func;            //對(duì)象反序列化方法

    /* 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);    //實(shí)例化該類時(shí)調(diào)用的方法,默認(rèn)為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;                    //類的接口數(shù)量
    uint32_t num_traits;                        //類的特性數(shù)量

    /* 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;           //特性別名數(shù)組
    zend_trait_precedence **trait_precedences;  //特性優(yōu)先級(jí)數(shù)組

    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位組合標(biāo)記對(duì)類進(jìn)行一些標(biāo)記苦银,不同的位有不同的標(biāo)記啸胧,如是否含有抽象方法赶站,是否為抽象類,是否為接口纺念,是否為特性等贝椿,具體的可以在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;         //位標(biāo)志陷谱,是否為靜態(tài)屬性記權(quán)限控制
    zend_string *name;      //經(jīng)過處理的屬性名烙博,比如私有屬性前加上類名。處理方法同序列化時(shí)的處理方法
    zend_string *doc_comment;//函數(shù)注釋
    zend_class_entry *ce;   //所屬類的指針
    zend_type type;
} zend_property_info;

哈希表表中的元素為bucket烟逊,bucket中有一個(gè)zval類型的val渣窜,val.value.ptr指向zend_property_info。zval中的ptr指針是void類型的宪躯,可以執(zhí)行任何類型的數(shù)據(jù)乔宿。

flags,屬性權(quán)限即是否靜態(tài)標(biāo)志

#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眷唉,這個(gè)值有兩個(gè)含義予颤,對(duì)于靜態(tài)變量它是索引,對(duì)于普通屬性它是偏移量冬阳。通過這個(gè)索引/偏移量可以在相應(yīng)的數(shù)組中找到值蛤虐。這里可能不好理解,看下面object代碼就明白了

struct _zend_object {
    zend_refcounted_h gc;   
    uint32_t          handle; // TODO: may be removed ???//該object在全局全局對(duì)象符號(hào)表中的索引
    zend_class_entry *ce;       //object所屬class指針           
    const zend_object_handlers *handlers;   //初始化時(shí)默認(rèn)指向全局變量std_object_habdlers肝陪,包括操作對(duì)象屬性等的多個(gè)指針函數(shù)
    HashTable        *properties;           //動(dòng)態(tài)普通屬性
    zval              properties_table[1];  //普通屬性值組
};

在object中并沒有儲(chǔ)存屬性名到屬性值之間的映射驳庭,那么怎么訪問屬性呢。實(shí)際上訪問object屬性時(shí)會(huì)首先到object.ce.properties_info中查找相應(yīng)key對(duì)應(yīng)zend_property_info中的offset氯窍。對(duì)于靜態(tài)屬性這是一個(gè)索引值饲常,通過object.ce.default_static_members_table[offset],就能獲取到屬性值狼讨,對(duì)于普通屬性這是一個(gè)相對(duì)于當(dāng)前object的偏移量贝淤,普通屬性保存在object.properties_table中,這是一個(gè)柔性數(shù)組政供,通過這個(gè)偏移量就能獲取到普通屬性播聪,普通屬性的offset=40,56布隔,72...离陶,因?yàn)閛bject大小為40字節(jié),所以object基址+40就是properties_table中第一個(gè)元素的地址衅檀,而每個(gè)zval16字節(jié)招刨,依次增加16就能訪問到后續(xù)元素。

通過這種方式object中只需要一個(gè)zval數(shù)組就能保存所有的普通屬性值哀军。如果按照直觀的想法沉眶,在object中使用HashTable保存屬性名到屬性值之間的映射打却,那么每一個(gè)普通屬性需要一個(gè)bucket,n個(gè)屬性就至少需要n個(gè)bucket+n個(gè)slot沦寂,bucket32字節(jié)学密,slot4字節(jié),共36字節(jié)传藏,bucket中的key指針指向一個(gè)zend_string腻暮,32字節(jié),因此每個(gè)普通屬性需要68字節(jié)內(nèi)存毯侦。而通過代碼中的這種方式哭靖,只需要一個(gè)zval數(shù)組,zval16字節(jié)侈离。那么每個(gè)對(duì)象的每個(gè)屬性都能節(jié)省52個(gè)字節(jié)內(nèi)存试幽。雖然因此導(dǎo)致class結(jié)構(gòu)內(nèi)存增加,但是類的數(shù)量遠(yuǎn)小于對(duì)象數(shù)量卦碾,采用這種方式可以減少很多內(nèi)存開銷铺坞。

還有一個(gè)動(dòng)態(tài)屬性組,這個(gè)HashTable保存的是運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建的屬性洲胖,如

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

這里的var2就是一個(gè)運(yùn)行時(shí)創(chuàng)建的動(dòng)態(tài)屬性济榨。由于類是在編譯階段創(chuàng)建的,這時(shí)還沒有創(chuàng)建動(dòng)態(tài)屬性var2绿映,因此該類的屬性表中沒有這個(gè)屬性擒滑,那么就不能通過和普通屬性一樣的方式保存。object用使用了一個(gè)單獨(dú)的properties來(lái)保存動(dòng)態(tài)屬性叉弦。

在class中還有一個(gè)屬性ZEND_MAP_PTR_DEF(zval *, static_members_table);這個(gè)屬性經(jīng)過宏替換后稱為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);
    }

初始化的時(shí)候只有這兩個(gè)地方出現(xiàn)了這個(gè)字段,看其他文章里說是給自定義class調(diào)用的淹冰,但是感覺和default_static_members_table沒什么區(qū)別库车,不知道有什么用。

class中default_properties_table保存的是普通屬性的默認(rèn)值也就是類定義時(shí)給出的值樱拴。

還有一個(gè)問題就是對(duì)于靜態(tài)變量凝颇,也和普通屬性一樣保存在properties_info中,然后取得zend_property_info中的offset疹鳄,再根據(jù)這個(gè)索引在default_static_members_table中取得值。但是static作為類變量芦岂,可以直接在class中用一個(gè)HashTable保存變量名到值得映射瘪弓,不需要像普通屬性一樣保存方式,這樣不光更節(jié)省內(nèi)存禽最,也減少了時(shí)間開銷腺怯。不知道為什么會(huì)和普通屬性用一樣得保存方式袱饭。

zend_class_entry結(jié)構(gòu)體很大但zend_object結(jié)構(gòu)體很小,因?yàn)榇蟛糠謹(jǐn)?shù)據(jù)都保存在class中呛占,這樣可以減少內(nèi)存開銷(class很少但object很多)虑乖。保存在object得數(shù)據(jù)主要有該對(duì)象得普通屬性值數(shù)組和動(dòng)態(tài)屬性映射表,其余的內(nèi)容包括普通屬性名晾虑,普通屬性映射關(guān)系疹味,靜態(tài)屬性,方法等都保存在class中帜篇。class中不保存屬性值糙捺,因?yàn)閷傩允莖bject的,當(dāng)存在繼承關(guān)系時(shí)笙隙,可能會(huì)出現(xiàn)變量名沖突洪灯,由于properties_table是一個(gè)數(shù)組,那么對(duì)于同名屬性只會(huì)保存一個(gè)竟痰,其他同名值在大部分時(shí)候并不會(huì)被刪除签钩,而是放在object的柔性數(shù)組properties中,通過控制properties_table中zend_property_info中的offset就能控制該屬性名對(duì)應(yīng)哪個(gè)屬性值坏快。

  • 父類屬性不與子類沖突 且 父類屬性是私有: 即父類屬性為private铅檩,且子類中沒有重名的,則將此屬性插入子類properties_info假消,但是更新其flag為ZEND_ACC_SHADOW柠并,這種屬性將不能被子類使用;
  • 父類屬性不與子類沖突 且 父類屬性是公有: 這種比較簡(jiǎn)單富拗,子類可以繼承使用臼予,直接插入子類properties_info;
  • 父類屬性與子類沖突 且 父類屬性為私有: 不繼承父類的啃沪,以子類原屬性為準(zhǔn)粘拾,但是打上ZEND_ACC_CHANGED的flag,這種屬性父子類隔離创千,互不干擾缰雇;
  • 父類屬性與子類沖突 且 父類屬性是公有或受保護(hù)的:
    • 父子類屬性一個(gè)是靜態(tài)一個(gè)是非靜態(tài): 編譯錯(cuò)誤;
    • 父子類屬性都是非靜態(tài): 用父類的offset追驴,但是值用子類的械哟,父子類共享;
    • 父子類屬性都是靜態(tài): 不繼承父類屬性殿雪,以子類原屬性為準(zhǔn)暇咆,父子類隔離,互不干擾;

由于對(duì)象的方法都保存在數(shù)組里爸业,因此訪問對(duì)象的方法時(shí)會(huì)進(jìn)入class中找到相應(yīng)的函數(shù)執(zhí)行其骄,同時(shí)要傳入$this為參數(shù)執(zhí)行當(dāng)前對(duì)象。因?yàn)樵赾lass中扯旷,所有的方法都保存在function_table中拯爽,那么也可以通過訪問靜態(tài)方法的方式訪問普通方法(class::func()),由于這種調(diào)用方式是直接在類上調(diào)用的钧忽,因此不會(huì)傳入$this參數(shù)毯炮,如果在調(diào)用的函數(shù)里沒有訪問$this變量,那么能執(zhí)行成功惰瓜,否則會(huì)因?yàn)檎也坏?this而報(bào)錯(cuò)否副。關(guān)于方法調(diào)用,《PHP7底層涉及與源碼實(shí)現(xiàn)》P151給出了一個(gè)很有趣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;
}

這段代碼會(huì)執(zhí)行到this->_version = "7.1.0";一行然后報(bào)錯(cuò)备禀。這里給php賦得地址是0,這個(gè)地址是操作系統(tǒng)得保留地址奈揍,用戶態(tài)程序是沒有權(quán)限訪問的曲尸,但是對(duì)version方法的調(diào)用仍然成功了,因?yàn)榉椒ū4嬖赾lass中而不是object中男翰,因此訪問Php類型的php對(duì)象的方法時(shí)另患,直接在全局符號(hào)表EG(class_table)找到Php類的結(jié)構(gòu),并且將$this(即(Php *)0)傳入調(diào)用version方法蛾绎。在version方法內(nèi)訪問了this->_version昆箕,但this這個(gè)對(duì)象無(wú)法訪問所以會(huì)在那一行報(bào)錯(cuò)。

對(duì)于class的繼承租冠,是在編譯完成后進(jìn)行的鹏倘,父類和字類分開編譯。所謂繼承也就是將父類的屬性顽爹、方法拷貝給字類纤泵,以及出現(xiàn)沖突時(shí)的處理。

對(duì)于普通屬性镜粤,先申請(qǐng)一個(gè)大小為父子屬性數(shù)之和的table數(shù)組捏题,然后將父類default_properties_table放在前,之類的放在后肉渴,然后釋放字類的default_properties_table并將table賦給該值公荧。對(duì)于靜態(tài)屬性操作類似。修改完值數(shù)組后同规,還需要修改幾個(gè)HashTable稚矿,即屬性的索引庸诱。由于前一步合并屬性值時(shí)父屬性在前,子屬性在后晤揣,因此對(duì)于靜態(tài)屬性子類properties_table值中zend_property_info中的offset需要+parent_ce->default_static_members_count,對(duì)于普通屬性朱灿,offset+=parent_properties_count*sizeof(zval)昧识,然后以和default_properties_table相同的順序合并properties_info。對(duì)沖突屬性的處理在上面已經(jīng)給出了盗扒。

對(duì)于常量跪楞,直接合并常量HashTable,沖突的用字類覆蓋父類侣灶。

對(duì)于方法也類似甸祭,不過加上了對(duì)final、abstract等方法的判定和處理褥影。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末池户,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子凡怎,更是在濱河造成了極大的恐慌校焦,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件统倒,死亡現(xiàn)場(chǎng)離奇詭異寨典,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)房匆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門耸成,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人浴鸿,你說我怎么就攤上這事井氢。” “怎么了赚楚?”我有些...
    開封第一講書人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵毙沾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我宠页,道長(zhǎng)左胞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任举户,我火速辦了婚禮烤宙,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘俭嘁。我一直安慰自己躺枕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拐云,像睡著了一般罢猪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叉瘩,一...
    開封第一講書人閱讀 52,328評(píng)論 1 310
  • 那天膳帕,我揣著相機(jī)與錄音,去河邊找鬼薇缅。 笑死危彩,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泳桦。 我是一名探鬼主播汤徽,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼灸撰!你這毒婦竟也來(lái)了谒府?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤梧奢,失蹤者是張志新(化名)和其女友劉穎狱掂,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體亲轨,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡趋惨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了惦蚊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片器虾。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蹦锋,靈堂內(nèi)的尸體忽然破棺而出兆沙,到底是詐尸還是另有隱情,我是刑警寧澤莉掂,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布葛圃,位于F島的核電站,受9級(jí)特大地震影響憎妙,放射性物質(zhì)發(fā)生泄漏库正。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一厘唾、第九天 我趴在偏房一處隱蔽的房頂上張望褥符。 院中可真熱鬧,春花似錦抚垃、人聲如沸喷楣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)铣焊。三九已至逊朽,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間曲伊,已是汗流浹背惋耙。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留熊昌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓湿酸,卻偏偏與公主長(zhǎng)得像婿屹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子推溃,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359