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保存的是运行时动态创建的属性,如
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
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等方法的判定和处理。