PHP 内核中的变量
PHP 变量在内核中的存储方式
在 Zend 引擎中是怎么可以做到一个变量保存任何的数据类型呢?打开 Zend/zend.h
头文件,会发现以下一些结构体:
typedef struct _zval_struct zval;
typedef union _zvalue_value {
long lval; // long, bool, resource
double dval; // double
struct { // string(len 保存字符串长度,val 保存字符串的值)
char *val;
int len;
} str;
HashTable *ht; // array
zend_object_value obj; // object
} zvalue_value;
struct _zval_struct {
zvalue_value value; // 变量的值
zend_uint refcount; // 变量引用数
zend_uchar type; // 变量的类型
zend_uchar is_ref; // 变量是否被引用
};
zval
结构体就是通常用到的 PHP 变量在内核中的表示方式。在 zval
结构体中,可以看到 4 个成员变量。
zval
结构体的 value 成员变量是一个 zvalue_value
联合体,从 zvalue_value
联合体的成员变量中可以看出,不同的类型会保存到不同的成员变量中,这样就实现了 PHP 变量可以存储任何数据类型。
引用计数器与写时复制
zval
结构中有以下两个成员变量用于引用计数器:
- is_ref: BOOL 值,标识变量是否是引用集合。
- refcount: 计算指向引用集合的变量个数。
一个 zval
结构的实体称为 zval 容器。在 PHP 语言层创建一个变量就会相应地在 PHP 内核中创建一个 zval 容器。
<?php
$a = "this is variable";
xdebug_debug_zval('a');
// 输出 --> a: (refcount=1, is_ref=0),string 'this is variable' (length=16)
$b = $a; // 此时 $a 和 $b 指向同一内存块
xdebug_debug_zval('a');
// 输出 --> a: (refcount=2, is_ref=0),string 'this is variable' (length=16)
$a = "changed value";
xdebug_debug_zval('a');
// 输出 --> a: (refcount=1, is_ref=0),string 'changed value' (length=13)
当将变量 $a 的值赋给变量 $b 时,变量 $a 的 refcount 增加 1,所以这时候变量 $a 和变量 $b 是指向同一内存块的。当改变变量 $a 的值时,发现 refcount 的值变回 1,所以这时候变量 $a 和变量 $b 指向不同的内存块,这就是 写时复制机制。就是两个指向同一内存块的变量,当其中一个变量的值发生变化,才会另外创建一个内存块去保存新的值。
<?php
$a = 1;
xdebug_debug_zval('a');
// 输出 --> a: (refcount=1,is_ref=0),int 1
$b = &$a;
xdebug_debug_zval('a');
// 输出 --> a: (refcount=2,is_ref=1),int 1
$b += 5;
xdebug_debug_zval('a');
// 输出 --> a: (refcount=2,is_ref=1),int 6
当显式地让一个变量引用另一个变量时,变量的 is_ref 字段会设置为 1,表示此变量被引用,另外引用计数器(refcount)也相应地加 1。