PHP7中zval的变化

背景

前几天在研究PHP引用变量的时候敲测试代码发现了一个很神奇的现象。在使用Xdebug扩展中的xdebug_debug_zval()函数查看变量的引用计数refcount的时候,发现PHP7和PHP5.4输出的结果竟然不一样,而且PHP7中输出的refcount竟然是0。具体测试代码和输出结果如下:

1
2
3
4
echo 'Version: '.PHP_VERSION.PHP_EOL;
$a = 'Hello World';
debug_zval_dump($a);
xdebug_debug_zval('a');

PHP5.6输出:

Version: 5.6.27
string(11) “Hello World” refcount(2)
a: (refcount=1, is_ref=0)=’Hello World’

PHP7.0输出:

Version: 7.0.12
string(11) “Hello World” refcount(1)
a: (refcount=0, is_ref=0)=’Hello World’

解决问题

经过坚持不懈的查找相关资料,终于在万能的stackoverflow中找到nikic大佬关于这个问题的回答,回答如下:

In PHP 7 a zval can be reference counted or not. There is a flag in the zval structure which determined this.

There are some types which are never refcounted. These types are null, bool, int and double.

There are other types which are always refcounted. These are objects, resources and references.

And then there are types, which are sometimes refcounted. Those are strings and arrays.

For strings the not-refcounted variant is called an “interned string”. If you’re using an NTS (not thread-safe) PHP 7 build, which you typically are, all string literals in your code will be interned. These interned strings are deduplicated (i.e. there is only one interned string with a certain content) and are guaranteed to exist for the full duration of the request, so there is no need to use reference counting for them. If you use opcache, these strings will live in shared memory, in which case you can’t use reference counting for them (as our refcounting mechanism is non-atomic). Interned strings have a dummy refcount of 1, which is what you’re seeing here.

For arrays the not-refcounted variant is called an “immutable array”. If you use opcache, then constant array literals in your code will be converted into immutable arrays. Once again, these live in shared memory and as such must not use refcounting. Immutable arrays have a dummy refcount of 2, as it allows us to optimize certain separation paths.


原谅我用蹩脚的英语水平翻译一下:
在PHP7中,zval结构体中有一个标志来决定zval是否能被引用计数。
像null,bool,int,double这些变量类型永远不会被引用计数(这个地方可能有些不太严谨,鸟哥的博客中写道PHP7中zval的类型共有18种,其中IS_LONG,IS_DOUBLE,IS_NULL,IS_FALSE,IS_TRUE不会使用引用计数)。
像object,resources,references这些变量类型总是会使用引用计数。
然而,像array,strings这些变量类型有时会使用引用计数,有时则不会。
不使用引用计数的字符串类型被叫做“interned string(保留字符串)”。如果你使用一个NTS(非线程安全)的PHP7来构建,通常情况下,代码中的所有字符串文字都将是限定的。这些保留字符串都是不可重复的(即,只会存在一个含有特定内容的保留字符串)。它会一直存在直到请求结束时才销毁,所以也就无需进行引用计数。如果使用了 opcache 的话,保留字符会被存储在共享内存中,在这种情况下,无法使用引用计数(因为我们引用计数的机制是非原子的)。保留字符串的伪引用计数为1。
对于数组来说,无引用计数的变量称为“不可变数组”。如果使用opcache,则代码中的常量数组文字将转换为不可变数组。同样的,他们存在于共享内存中,因此不得使用引用计数。不可变数组的伪引用数为2,因为它允许我们优化某些分离路径。
另外,关于PHP7中zval变化更加详细的阐述请看我转载的两篇博文:
【转】变量在PHP7内部的实现(一)
【转】变量在PHP7内部的实现(一)