Skip to content

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。

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*