基本结构
一个zval(Zend value的简写)表示任意的一个php值。因此,它可能是所有PHP中最重要的结构,你将经常使用到它。本节将介绍zvals及其使用背后的基本概念。
类型和值
除此之外,每个zval存储一些值和该值具有的类型。这是必要的,因为php是一门动态类型语言,因此变量类型在运行时才会知道而不是在编译期间。另外在一个zval的生命周期中类型是可以被改变的,所以如果一个zval上一次存储的是一个int类型的值可能在之后又包含了一个string类型的值。
类型是作为一个整型标示存储的(一个无符号整型)。它可以是几个值之中的一个,一些值和php中的8种类型变量相对应,其他仅用于内部引擎使用。这些值使用IS_TYPE形式的常量引用,比如,IS_NULL对应null类型,IS_STRING对应字符串类型。
真实的值是用一个union存储的,其定义如下:
typedef union _zend_value {
zend_long lval;
double dval;
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;
} zend_value;
对于不熟悉union概念的人:一个union定义了多个不同类型的成员,但一次只能使用其中一个。比如,如果value.lval成员被赋值了,然后你需要使用value.lval去查找值而不是其他成员(这样做会违反“严格混叠”的保证,并导致未定义的行为)。原因是unions在同一内存位置存储所有的成员,并根据你访问的不同成员解释此处的值。union的大小由最大的成员的大小决定。
在使用zvals时,类型标识用来查找当前正在使用的是union的哪个成员,在看看用于这样做的API之前,让我们来看一下PHP支持的不同类型以及它们的存储方式:
最简单的类型是IS_NULL:它不需要实际存储任何值,因为只有一个null值。
对于数字的存储,PHP提供了IS_LONG和IS_DOUBLE类型,它们分别使用了zend_long lval和double dval成员。前者用来存储整型,后者存储浮点数。
关于zend_long类型有一些东西需要注意:首先,它是一个有符号的整数类型,也就是它能存储正整数以及负整数,但通常不太适合进行按位操作。第二,zend_long是和平台无关的,无论你正在使用哪个平台,在32位平台zend_long总是占4个字节,在64位平台总是占8个字节。
除此之外,你可以使用和longs相关的宏,SIZEOF_ZEND_LONG或者ZEND_LONG_MAX。更多信息看Zend/zend_long.h源码。
double类型用来存储符合8个字节大小的IEEE-754规范的浮点型数字。这个格式的细节不会在这里讨论,但是你应该至少知道这种类型的精度有限,通常不要存储你想要的精确值。
布尔值使用IS_TRUE或IS_FALSE标识,并且不需要存储任何更多的信息。存在一个叫_IS_BOOL的“伪类型”标志,但你不应该作为一个zval类型来使用它,这是不正确的。这种伪类型在一些不常见的内部场景(如类型提示)中使用。
剩下的四种类型只会在这里快速提一下,并在自己的章节中进行更详细的讨论:
字符串(IS_STRING)是存储在一个叫zend_string的结构体中,它们由存储字符串的char*和存储字符串长度的size_t组成。你将在字符串一章中找到有关zend_string结构及其特有的API的更多信息。
数组使用IS_ARRAY标识,存储在zend_array *arr成员中。在Hashtables章节将介绍HashTable结构是如何工作的。
对象(IS_OBJECT)使用zend_object *obj成员。PHP中的类和对象系统将在Class and Objects章节介绍。
资源(IS_RESOURCE)是一个特殊的类型,使用zend_recource *res成员。将在Resource章节介绍资源类型。
简而言之,这里有一个表,其中包含所有可用的类型标签及其值的相应存储位置:
Type tag | Storage location |
---|---|
IS_NULL | none |
IS_TRUEorIS_FALSE | none |
IS_LONG | zend_longlval |
IS_DOUBLE | doubledval |
IS_STRING | zend_string*str |
IS_ARRAY | zend_array*arr |
IS_OBJECT | zend_object*obj |
IS_RESOURCE | zend_resource*res |
特殊类型
你可能会看到我们还没有介绍的zvals里面的其他类型。这些类型是特殊的类型,本来就不存在的在php用户空间,仅在内部引擎使用案例中使用。zval结构被认为是非常灵活的,在内部几乎可以存储任何感兴趣的数据类型,不仅仅是我们上面刚刚介绍的PHP特定类型。
特殊的IS_UNDEF类型具有特殊的意义。意味着“这个zval没有包含任何感兴趣的数据,不能访问任何数据字段”。它是提供给zvals/memory_management使用的。如果你看到了一个IS_UNDEF zval,意味着它不是特殊类型,不包含有效信息。
zend_refcounted *计数字段不是非常容易去理解,基本上,该字段作为其他任何可引用类型的头使用(这里翻译可能不太准确)。 这在zvals/memory_management章节做了详细介绍。
zend_reference *ref 用来表示一个php引用。然后使用IS_REFERENCE类型标识。 在这里,我们还有专门的一个章节来介绍这个概念,请看zvals/memory_management章节。
当你从编译器操作AST时,会使用zend_ast_ref * ast。php编译细节将在/php7/compiler章节详细介绍。
zval *zv是仅在内部使用。你不应该去操作它。它通常和IS_INDRECT一起使用,允许你嵌入一个zval *到另一个zval里面。这个字段有一个非常特别的黑暗用法,去表示PHP的超全局变量$GLOBALS[]。
void *ptr字段有时候是非常有用的。同样地只能在内部使用,不能在php用户空间使用。基本上当你想要存储任何值到一个zval你将用到这个字段。是的,它是一个void指针,在C语言里表示可以指向任何大小的内存区域,可以包含任何内容。然后在zval中使用IS_PTR标志类型。
当你看/php7/classes_objects章节的时候,你将学习zend_class_entry类型。zval的zend_class_entry类型是用来保存一个php类引用到一个zval。同样,不能直接在php用户空间直接使用,只能在内部需要的时候使用。
最后,zend_function *fun字段是用来嵌入一个php函数进一个zval。在/php7/functions章节会详细介绍PHP Functions。