基本结构

一个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。

results matching ""

    No results matching ""