PHP引用计数基础

在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. 对象可以看作一个指针,指向具体对象,同样按照上述原则执行

推荐阅读更多精彩内容