记得网上流传甚广的段子 "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, 传入的参数是对象时, 会获取对象的属性, 所以遍历对象就是在变量对象的属性.
来源: https://www.cnblogs.com/enochzzg/p/9626946.html