深入理解PHP内核之ZVAL

PHP是一种弱类型语言,但是弱类型并不代表PHP变量就没有类型区分

PHP的类型有8种

  • 标量类型:boolen(布尔),integer(整型),float(浮点),string(字符)
  • 复合类型:array(数组),object(对象)
  • 特殊类型:resource(资源) null

PHP可以不声明变量类型,可以在运行时直接赋值。并且在运行期间可以由一种类型转换成另一种类型。

如下例,没有声明的情况下,$variable 可以赋任意类型的值。

1
2
3
$variable = 0;
$variable = 'hello world.';
$variable = true;

在PHP引擎内,变量都是用一个结构体来表示。

这个结构体具体代码在: 点击传送

ZVAL 基本结构为:

1
2
3
4
5
6
7
struct _zval_struct {
/* Variable information */
zvalue_value value; /* 存储变量的值 */
zend_uint refcount__gc; /* 引用计数 */
zend_uchar type; /* 变量的类型 */
zend_uchar is_ref__gc; /* 是否为引用 */
};

zval_value value

这里就是变量的实际值,类型是一个 zvalue_value 联合体。
1
2
3
4
5
6
7
8
9
10
11
typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
zend_object_value obj;
zend_ast *ast;
} zvalue_value;

zend_uint refcount__gc

实际是一个计数器,用来保存有多少变量指向该zval。在变量生成时,会置为1,也就是说 refcount__gc = 1
对变量进行操作会改变它的值。如 $a = $b 会使 refcount 加 1,而 unset() 操作会减 1

zend_uchar type

这个字段表示变量的类型,前面说过 PHP 有8种类型,在 PHP zend 引擎中,对应下面的宏

该type字段会决定上面 zval_value 联合体储存的方式

1
2
3
4
5
6
7
8
9
10
11
#define IS_NULL		0
#define IS_LONG 1
#define IS_DOUBLE 2
#define IS_BOOL 3
#define IS_ARRAY 4
#define IS_OBJECT 5
#define IS_STRING 6
#define IS_RESOURCE 7
#define IS_CONSTANT 8
#define IS_CONSTANT_AST 9
#define IS_CALLABLE 10

zend_uchar is_ref__gc

标志是否为引用,当使用 & 对一个变量操作时,如 $a = &$b, is_ref__gc 会被设置为1。

zval如何在PHP中运行

我们在创建一个PHP变量时实际上在PHP中是创建了一个zval

1
2
3
// 下面代码需要安装 xdebug 扩展
$a = 'hello world';
xdebug_debug_zval('a');

输出结果

1
a: (refcount=1, is_ref=0)='hello world'

对应上面的zval结构体,也就是

1
2
3
4
value = 'hello world';
refcount__gc = 1;
type = IS_STRING;
is_ref__gc = 0;

我们把$a赋值给另一个变量时,会增加zval的refcount__gc

1
2
3
4
5
$a = 'hello world';
xdebug_debug_zval('a');
$b = $a;
xdebug_debug_zval('a');
xdebug_debug_zval('b');

输出结果

1
2
3
a: (refcount=1, is_ref=0)='hello world'
a: (refcount=2, is_ref=0)='hello world'
b: (refcount=2, is_ref=0)='hello world'

可以看到$a和$b的结构是一样的,它们都指向同一个zval

使用unset时

1
2
3
4
5
$a = 'hello world';
$b = $a;
xdebug_debug_zval('a');
unset($b);
xdebug_debug_zval('a');

输入结果

1
2
a: (refcount=2, is_ref=0)='hello world'
a: (refcount=1, is_ref=0)='hello world'

使用 & 时

1
2
3
$a = 'hello world';
$b = &$a;
xdebug_debug_zval('a');

输入结果

1
a: (refcount=2, is_ref=1)='hello world'

后记

在 PHP7 中,zval结构体发生了重大变化,有兴趣的可以参考 鸟哥-深入理解PHP7之zval

-------------本文结束感谢您的阅读-------------