在zval中存储了两个重要字段,is_ref和refcount。
is_ref为true时,表示变量是引用变量,否则为普通变量。
refcount表示,变量的引用次数。
通过xdebug_debug_zval可以查看“refcount”和“is_ref”的值。
一般数据类型
一般数据类型的行为较为简单,单独一个变量的is_ref为false,refcount为1。
如:
$a = "new string";
xdebug_debug_zval('a');
会输出:
a: (refcount=1, is_ref=0)='new string'
当赋值给新的变量时,如果是普通赋值,则refcount++
如:
$a = "new string";
$b = $a;
xdebug_debug_zval( 'a' );
会输出:
a: (refcount=2, is_ref=0)='new string'
如果是引用赋值,则refcount++,且is_ref会变为true
如:
$a = "new string";
$b = &$a;
xdebug_debug_zval( 'a' );
会输出:
a: (refcount=2, is_ref=1)='new string'
通过unset()可以减少引用计数
如:
$a = "new string";
$c = $b = $a;
xdebug_debug_zval( 'a' );
unset( $b, $c );
xdebug_debug_zval( 'a' );
会输出:
a: (refcount=3, is_ref=0)='new string'
a: (refcount=1, is_ref=0)='new string'
如果经过unset()后,refcount的值变为1,则is_ref会变为false
如:
$a = "new string";
$b = &$a;
xdebug_debug_zval( 'a' );
unset($b);
xdebug_debug_zval( 'a' );
会输出:
a: (refcount=2, is_ref=1)='new string'
a: (refcount=1, is_ref=0)='new string'
普通变量按照copy on write执行,某个变量修改后,会与其他变量分离:
$a = "new string";
$b = $a;
$c = $b;
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'c' );
$c = "another string";
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'c' );
会输出:
a: (refcount=3, is_ref=0)='new string'
c: (refcount=3, is_ref=0)='new string'
a: (refcount=2, is_ref=0)='new string'
c: (refcount=1, is_ref=0)='another string'
引用变量按照change on write执行,某个变量改变后,其余变量也会相应改变
如:
$a = "new string";
$b = &$a;
$c = &$b;
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'c' );
$c = "another string";
xdebug_debug_zval( 'a' );
xdebug_debug_zval( 'c' );
会输出:
a: (refcount=3, is_ref=1)='new string'
c: (refcount=3, is_ref=1)='new string'
a: (refcount=3, is_ref=1)='another string'
c: (refcount=3, is_ref=1)='another string'
普通变量变为引用变量也会被当作值的变化,会引发copy on write。
如:
$a = 1;
$b = $a;
$c = &$b;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
会输出:
a: (refcount=1, is_ref=0)=1
b: (refcount=2, is_ref=1)=1
c: (refcount=2, is_ref=1)=1
如:
$a = 1;
$b = &$a;
$c = $b;
xdebug_debug_zval('a');
xdebug_debug_zval('b');
xdebug_debug_zval('c');
会输出:
a: (refcount=2, is_ref=1)=1
b: (refcount=2, is_ref=1)=1
c: (refcount=1, is_ref=0)=1
数组引用
如:
$a = array( 'meaning' => 'life', 'number' => 42 );
xdebug_debug_zval( 'a' );
会输出:
a: (refcount=1, is_ref=0)=array (
'meaning' => (refcount=1, is_ref=0)='life',
'number' => (refcount=1, is_ref=0)=42
)
如:
$a = array( 'meaning' => 'life', 'number' => 42 );
$a['life'] = $a['meaning'];
xdebug_debug_zval( 'a' );
会输出:
a: (refcount=1, is_ref=0)=array (
'meaning' => (refcount=2, is_ref=0)='life',
'number' => (refcount=1, is_ref=0)=42,
'life' => (refcount=2, is_ref=0)='life'
)
对于循环引用:
$a = array( 'one' );
$a[] =& $a;
xdebug_debug_zval( 'a' );
输出:
a: (refcount=2, is_ref=1)=array (
0 => (refcount=1, is_ref=0)='one',
1 => (refcount=2, is_ref=1)=...
)
对象
// $a 是指向 Foo object 0 的一个指针p0
$a = new Foo;
// $b 是指向 Foo object 0 的另一个指针p1
$b = $a;
// $c 和 $a 是p0的引用
$c = &$a;
// $a 和 $c 是p0的引用,但是p0改为指向Foo object 1, $b 不变
$a = new Foo;
// $c 是指向 Foo object 1的指针p0
unset($a);
// $a 和 $b 是p1的引用
$a = &$b;
// p1 变为 NULL,$a 和 $b 都是引用. Foo object 0 可以被GC
$a = NULL;
// $b 不存在了, $a 是p1,值为 NULL
unset($b);
// $a 是指向 Foo object 2的指针p1 , $c是指向 Foo object 1的指针p0
$a = clone $c;
// Foo object 1可以被gc.
unset($c);
// $c 是指向Foo object 2的指针p2,$a 是指向 Foo object 2的指针p1
$c = $a;
// $c 是p2, 仍然指向Foo object 2
unset($a);
// $a 和 $c 都是p2的引用
$a = &$c;
const ABC = TRUE;
if(ABC) {
// Foo object 2 可以被gc
$a = NULL;
} else {
// $c 仍然指向Foo object 2
unset($a);
}
总结
1. is_ref表示变量是否为引用变量,refcount表示变量引用次数
2. 普通变量被其他变量引用时,变成is_ref变为true
3. 经过unset()后,refcount变成1时,is_ref会被置为false
4. 普通变量按照copy on write执行,某个变量的值改变时,会单独复制一份,再改变值
5. 引用变量按照change on write执行,某个变量的值改变时,其余相关的值也会改变
6. 数组里的每一个元素都按照上述原则执行
7. 对象可以看作一个指针,指向具体对象,同样按照上述原则执行