内核中变量的存储方式
首先看两个C的结构:
typedef struct _zval_struct {
zvalue_value value;
zend_uint refcount__gc;
zend_uchar type;
zend_uchar is_ref__gc;
} zval;
typedef union _zvalue_value {
long lval; // 用于 bool 类型、整型和资源类型
double dval; // 用于浮点类型
struct { // 用于字符串
char *val;
int len;
} str;
HashTable *ht; // 用于数组
zend_object_value obj; // 用于对象
zend_ast *ast; // 用于常量表达式(PHP5.6 才有)
} zvalue_value;
php弱类型的实现在于以上的C片段。简单解释一下原理:
php每申明一个变量,zval会给变量一个容器,例如 $a=1; 那么系统会给int类型的a 一个容器。容器内部包含以下内容:
a的值--对应的结构体中的zvalue_value value
a的类型--对应结构体的 zend_uchar type
zend 引擎定义如下几种类型
常量定义 | 标识类型 |
#define IS_NULL | 是否为空(null) |
#define IS_LONG | 是否为整型(int) |
#define IS_DOUBLE | 是否为浮点数(float) |
#define IS_STRING | 是否为字符串(string) |
#define IS_ARRAY | 是否为数组(array) |
#define IS_OBJECT | 是否为对象(object) |
#define IS_BOOL | 是否为布尔类型(bool) |
#define IS_RESOURCE | 是否为资源类型(resource) |
不同类型对应结构体的存储:
php语言层类型 | 保存在zvalue_value中的成员变量 |
long、bool、resource | lval |
double | dval |
string | str(len保存长度、val保存值) |
array | ht |
object | obj |
a这个容器是否为引用地址 --对应的是zend_uchar is_ref__gc
a被指向的集合数 --对应的是 zend_uint refcount__gc
当你申明一个变量 $a=1 系统做了如下的事情
创建结构体 -> 设置类型type 为 int -> 设置值为zvalue_value里的long lval(string类型等其他类型依次类推) -> 设置是否引用地址为 0 ,即不引用内存地址 -> a的指向集合数设置为1 -> 将zval容器指向 $a
如果此时,我们将 $b=$a 那么 系统会作如下处理
根据$a 找到对应结构体 -> 设置指向集合数自增 1 -> 将zval 容器 指向 $b
如果 我们在上述基础上使用 $c=& $a 设置c的值捆绑a的地址,系统会做如下处理
根据$a 找到对应结构体 -> 设置指向集合数再次自增 1 -> 设置是否引用地址 为 1 -> 将该zval 容器 指向 $c
此时如果我们 跟新 $c的值为 2 试着sh使用 xdebug_debug_zval打印 a的值会出现如下内容
a: (refcount=2, is_ref=1)=2
原因:
因为三个变量都指向zval 的容器。并且 $c=& $a 会更改 zval 模式为引用模式。当其中一个变量值修改时 其他值由于指向同一个zval容器。所以都会改变。
引用计数与写时复制
如果执行以下代码:
<?php
$a=1;
xdebug_debug_zval('a');
$b=$a;
xdebug_debug_zval('a');
$a=2;
xdebug_debug_zval('b');
xdebug_debug_zval('a');
那么结果如下 :
a: (refcount=1, is_ref=0)=1
a: (refcount=2, is_ref=0)=1
b: (refcount=1, is_ref=0)=1
a: (refcount=1, is_ref=0)=2
过程分析:
$a 创建了 指向数为1 非地址引用并且值为int类型 1 的zval 容器;新建一个$b 也指向 $a的zval 容器。现在我们将$a的值赋值为 2 ,由于该zval 容器非地址引用,所以系统会重新创建一个指向数为1 非地址引用并且值为int类型 2的zval 容器 指向$a ,由于 $a 不再指向原zval容器 ,所以原容器的 指向数 减 1。
以上内容 就是php内核的另一个重要的特性。引用计数 与 写时复制 原理。这个原理很好的解决了 内存的复用。