这篇文章主要介绍的内容是关于PHP内核之zval,有着一定的参考价值,现在分享给大家,有需要的朋友可以参考一下
原文地址
作者:Twei 主页
之前面试的时候面试官问过php中变量是如何实现的,遗憾的是只答道了大概是用结构体实现的。这篇文章是谷歌之后觉得总结 的比较到位的,故转载进而学习之。
PHP中的数据类型
相对于 C、 C++、 Java等其他编程语言,PHP 是一个弱类型的语言,意味着当我们要使用一个变量时,不需要去声明它的类型。这个特性给我们带来了很多便利,同时有时也会带来一些陷阱。那么,PHP 是真的没有数据类型这个说法吗?
当然不是。在 PHP 官方文档中将 PHP 中的变量划分为三类:标量类型、复杂类型和特殊类型。标量类型包括布尔型(bool)、整型(int)、浮点型(float)和字符串(string);复杂类型包括数组(array)和对象(object);特殊类型包括 NULL 和资源(resource)。所以说 PHP 的变量细分的话,有 8 种数据类型。
总所周知,PHP 的底层是用 C 语言实现的。我们的 PHP 脚本会经过 Zend 引擎解析为 C 代码再执行。那么,一个 PHP 的变量,在 C 语言上是怎么表示的呢?它最终会被解析成什么样呢?
答案就是 zval。 不管什么类型的 PHP 变量,在 PHP 源代码中统一用一个叫做 zval 的结构表示。 zval 可以看做是 PHP 变量在 C 代码中的容器,它存储了这个变量的值、类型等相关信息。
那么我们就看一下 zval 的基本结构(需要一点 C 语言的基本知识)。
zval的基本结构
在 PHP 源代码中 zval 这个结构是一个名叫 _zval_struct
的结构体(struct),具体定义在源代码的 Zend/zend.h
文件中,下面是相关代码的摘录:
struct _zval_struct { zvalue_value value; /* value */ zend_uint refcount__gc; /* value of ref count */ zend_uchar type; /* active type */ zend_uchar is_ref__gc; /* if it is a ref variable */ }; typedef struct _zval_struct zval;
也就是说,在 PHP 的源码中,就用这一个结构体表示 PHP 中各种类型的变量,并且还可以实现其他的一些功能,例如垃圾回收(GC:Grabage Collection)。
可以看到它由 4 个字段构成,分别表示这个变量的某个信息。
value 用来表示变量的实际值,具体来说它是一个 zvalue_value 的联合体(union):
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { /* string */ char *val; int len; } str; HashTable *ht; /* hash table value,used for array */ zend_object_value obj; /* object */} zvalue_value;
可以看到 _zvalue_value 中只有 5 个字段,但是 PHP 中有 8 种数据类型,那么如何用 5 个字段表示 8 种类型呢?
这算是 PHP 设计比较巧妙的一个地方,它通过复用字段达到了减少字段的目的。例如,在 PHP 内部布尔型、整型及资源(只要存储资源的标识符即可)都是通过 lval 字段存储的;dval 用于存储浮点型;str 存储字符串;ht 存储数组(注意 PHP 中的数组其实是哈希表);而 obj 存储对象类型;如果所有字段全部置为 0 或 NULL则表示 PHP 中的 NULL,这样就达到了用 5 个字段存储 8 种类型的值。
从它的后缀 gc 可以看到,这个字段是和垃圾回收相关的。
它实际上是一个计数器,用来保存有多少变量指向该zval。在变量生成时,置为1,也就是 refcount = 1。
对变量进行不同的操作会改变它的值。典型的赋值操作如
b 会使 refcount 加 1,而 unset() 操作会相应的减 1。
通过判断它的值可以进行垃圾回收。在 PHP5.3 之前,使用引用计数的机制来实现 GC:如果一个 zval 的 refcount 减为 0,那么 Zend 引擎会认为没有任何变量指向该 zval,就会释放该 zval 所占的内存空间。但仅仅使用引用计数机制无法释放掉循环引用的 zval,这是就会导致内存泄露(Memory Leak)。
在 5.3 以前,这个字段的名字还叫做 refcount,5.3 以后,在引入新的垃圾回收算法来对付循环引用,作者加入了大量的宏来操作 refcount,为了能让错误更快的显现,所以改名为 refcount__gc, 迫使大家都使用宏来操作 refcount。
类似的, 还有第四个字段 is_ref, 这个值表示了 PHP 中的一个类型是否是引用。
想了解 PHP 的垃圾回收机制,可以参考这篇博客:PHP的垃圾回收机制
注:变量,也可以被称为符号,symbol。所有的符号都存在符号表(symbol table)中, 不同的作用域使用不同的符号表,关于这一点,这篇博客进行了讲解。
这个字段用于表明变量属于 PHP 8 种类型的哪种。在 zend 内部,这些类型对应于下面的宏(代码位置 phpsrc/Zend/zend.h):
#define IS_NULL 0#define IS_LONG 1#define IS_DOUBLE 2#define IS_BOOL 3#define IS_ARRAY 4#define IS_OBJECT 5#define IS_STRING 6#define IS_RESOURCE 7#define IS_CONSTANT 8#define IS_CONSTANT_ARRAY 9#define IS_CALLABLE 10
这个字段用于标记变量是否是引用变量。对于普通的变量,该值为 0,而对于引用型的变量,该值为 1。这个变量会影响 zval 的共享、分离等。它也和 PHP 的垃圾回收有关。
上述的 zval 结构,随着时间的发展,暴露出许多问题,例如占用空间大(24 字节)、不支持拓展、 对象和引用效率差等,所以在 PHP7 的时候,对 zval 进行了较大的改变,现在它的结构是这样的:
struct _zval_struct { union { zend_long lval; /* long value */ double dval; /* double value */ zend_refcounted *counted; zend_string *str; zend_array *arr; zend_object *obj; zend_resource *res; zend_reference *ref; zend_ast_ref *ast; zval *zv; void *ptr; zend_class_entry *ce; zend_function *func; struct { uint32_t w1; uint32_t w2; } ww; } value; union { struct { ZEND_ENDIAN_LOHI_4( zend_uchar type, /* active type */ zend_uchar type_flags, zend_uchar const_flags, zend_uchar reserved) /* call info for EX(This) */ } v; uint32_t type_info; } u1; union { uint32_t var_flags; uint32_t next; /* hash collision chain */ uint32_t cache_slot; /* literal cache slot */ uint32_t lineno; /* line number (for ast nodes) */ uint32_t num_args; /* arguments number for EX(This) */ uint32_t fe_pos; /* foreach position */ uint32_t fe_iter_idx; /* foreach iterator index */ } u2; };
虽然看起来变得好大,但其实仔细看,它的字段都是联合体,这个新的 zval 在 64 位环境下,只需要 16 个字节(2 个指针 size)。PHP7 中的 zval,已经变成了一个值指针,它要么保存着原始值,要么保存着指向一个保存原始值的指针。
这部分内容来自鸟哥的GitHub。
zval 是一种 C 语言实现的数据结构,功能是作为 PHP 变量的容器;
它保存了变量的各种信息(如类型和值),并为其他功能(如垃圾回收)提供支持;
在不同的 PHP 版本中,它的结构不同。PHP7 的 zval 占 16 个字节,PHP5 的要占 24 个字节。
PHP内核探索之变量(1)变量的容器-Zval
PHP垃圾回收深入理解
深入理解PHP7之zval
相关推荐:
以上是PHP内核之zval的详细内容。更多信息请关注PHP中文网其他相关文章!