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等方法的判定和處理褥影。