跟厂长学PHP内核7(六):变量之zval

记得网上流传甚广的段子“PHP是世界上最好的语言”,暂且不去讨论是否言过其实,但至少PHP确实有独特优势的,比如它的弱类型,即只需要$符号即可声明变量,使得PHP入手门槛极低,成为大家所青睐的Web服务端语言。那么它的变量是如何实现的呢?我们今天就来学习一下PHP的基本变量。

一、引言

PHP的变量存储在zval结构体中,在执行阶段中编译为op_array时就能看到zval的身影。结构体定义在Zend/zend_types.h中,定义内容如下所示:

struct _zval_struct {
    zend_value        value;            /* value */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         /* active type */
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* 保留字段 */
        } 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;
};

二、结构体剖析

2.1、zend_value

结构体的第一个变量是zend_value,顾名思义,它其实也是一个结构体,用于存放变量的值,比如整型、浮点型、引用计数、字符串、数组、对象、资源等。zend_value定义了众多类型的指针,但这些类型并不都是变量的类型,有些是给内核自己使用的,比如指针ast、zv、ptr。

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;               /* zval类型 */
    void             *ptr;              /* 指针类型 */
    zend_class_entry *ce;               /* class类型 */
    zend_function    *func;             /* function类型 */
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

2.2、u1

u1是一个联合体,它联合了结构体v和整型type_info。下面我们先来看一下结构体v的构成。

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;

2.2.1、type

type是指变量的类型,刚在2.1中讲到了zend_value是用来存储变量的值,所以也应该有地方存储变量的类型,而这就是type的职责。以下是PHP定义的所有变量类型,有我们熟知的布尔、NULL、浮点、数组、字符串等类型。也有陌生的undef、indirect、ptr类型,变量类型在下一章中详解,这里不再赘述。

/* regular data types */
#define IS_UNDEF                    0
#define IS_NULL                     1
#define IS_FALSE                    2
#define IS_TRUE                     3
#define IS_LONG                     4
#define IS_DOUBLE                   5
#define IS_STRING                   6
#define IS_ARRAY                    7
#define IS_OBJECT                   8
#define IS_RESOURCE                 9
#define IS_REFERENCE                10

/* constant expressions */
#define IS_CONSTANT                 11
#define IS_CONSTANT_AST             12

/* fake types */
#define _IS_BOOL                    13
#define IS_CALLABLE                 14

/* internal types */
#define IS_INDIRECT                 15
#define IS_PTR                      17

2.2.2、type_flags

可以把它理解为子类型,上面提到了变量的类型,这个是针对不同类型的子类型或标记,type_flags一共有以下6种。

/* zval.u1.v.type_flags */
#define IS_TYPE_CONSTANT            (1<<0)  /* 常量 */
#define IS_TYPE_IMMUTABLE           (1<<1)  /* 不可变的类型 */
#define IS_TYPE_REFCOUNTED          (1<<2)  /* 需要引用计数的类型 */
#define IS_TYPE_COLLECTABLE         (1<<3)  /* 可能包含循环引用的类型 */
#define IS_TYPE_COPYABLE            (1<<4)  /* 可被复制的类型 */
#define IS_TYPE_SYMBOLTABLE         (1<<5)  /* 符号表类型 */

2.2.3、const_flags

常量类型的标记,对应的属性为:

/* zval.u1.v.const_flags */
#define IS_CONSTANT_UNQUALIFIED     0x010
#define IS_LEXICAL_VAR              0x020
#define IS_LEXICAL_REF              0x040
#define IS_CONSTANT_CLASS           0x080  /* __CLASS__ in trait */
#define IS_CONSTANT_IN_NAMESPACE    0x100  /* used only in opline->extended_value */

2.2.4、type_info

type_info与结构体v共用内存,修改type_info等同于修改结构体v的值,所以type_info是v中四个char的组合。

2.3、u2

本来使用u1和zend_value就可以表示变量的,没有必要定义u2,但是我们来看一下,如果没有u2,在内存对齐的情况下zval内存大小为16个字节,当联合了u2后依然是占用16个字节。既然有或没有占用内存大小相同,不如用它来记录一些附属信息。下面我们来看下u2都存储了哪些内容。

2.3.1、next

用来解决哈希冲突问题,记录冲突的下一个元素位置。

2.3.2、cache_slot

运行时缓存,在执行函数时回去缓存中查找,若缓存中没有则到全局function表中查找。

2.3.3、lineno

文件执行的行号,应用在AST节点上。Zend引擎在词法和语法解析时会把当前执行的文件行号记录下来,记录在zend_ast中的lineno中。

2.3.4、num_args

函数调用时传入函数的参数个数。

2.3.5、fe_pos

用于遍历数组时记录当前遍历的位置,比如每次执行foreach时fe_pos都会加一,当再次调用foreach进行遍历时,fe_post会进行重置。

2.3.6、fe_iter_idx

这个与fe_pos类似,只不过它是针对对象的。对象的属性也是HashTable,传入的参数是对象时,会获取对象的属性,所以遍历对象就是在变量对象的属性。

三、参考文献