这篇文章主要介绍的内容是关于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 个字段构成,分别表示这个变量的某个信息。
zvalue_value valuevalue 用来表示变量的实际值,具体来说它是一个 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 种类型的值。
zend_uint refcount__gc从它的后缀 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)中, 不同的作用域使用不同的符号表,关于这一点,这篇博客进行了讲解。
zend_uchar type这个字段用于表明变量属于 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
zend_uchar is_ref__gc这个字段用于标记变量是否是引用变量。对于普通的变量,该值为 0,而对于引用型的变量,该值为 1。这个变量会影响 zval 的共享、分离等。它也和 php 的垃圾回收有关。
php7中的zval上述的 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内核之探究内存管理与缓存机制
php内核分析-zend虚拟机详解
以上就是php内核之zval的详细内容。