大蛇写这篇文章是因为TIPI上关于PHP写时复制(Copy-On-Write)问题被同事发到群里,引起了我的兴趣。下面就把这个问题说出来,大家想想为什么: ?php$foo['love']= 1;$bar= $tipi= $foo;$tipi['love']= '2';echo $foo['love']; // 输出 2 相信很多人会认为这
大蛇写这篇文章是因为TIPI上关于PHP写时复制(Copy-On-Write)问题被同事发到群里,引起了我的兴趣。下面就把这个问题说出来,大家想想为什么:
<?php $foo['love'] = 1; $bar = &$foo['love']; $tipi = $foo; $tipi['love'] = '2'; echo $foo['love']; // 输出 2
相信很多人会认为这是一个BUG,为什么$foo['love']的值会被改变?在官方的邮件列表中,这个问题也被讨论烂了,与其说是特性,我更愿意说它是个BUG。因为一切有可能挖坑的动作都应该被规避。
为什么会有这样的差异?我们从这里谈开去。
变量类型:
PHP的变量类型有8种,其中NULL和resource是特殊类型,我们常用的有简单类型:int, float, string, boolean,和复合类型:array, object。
何谓简单类型?何谓复合类型?我们来看一看PHP是怎样实现变量的。
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; } zvalue_value; typedef struct _zval_struct zval; struct _zval_struct { zvalue_value value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc; };
#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
我们再看zval中的refcount__gc和 is_ref__gc。refcount__gc是一个计数器,而is_ref__gc则表示该变量是否为引用。那么
$a = &$b; $c=1;
的情况下,$a和$b的is_ref__gc值均为1;$c的is_ref__gc的值为0。
那么refcount__gc在什么时候用呢?那我们接下来说说变量的回收机制。
变量在unset的时候会被注销,那么他的值占用的内存是否马上释放呢?实际上不是,refcount__gc这个计数器就是做这个用途的。
PHP有个特性叫做写时复制(Copy-On-Write),例如:
$a = 1; $b = $a;
=============== 休息,休息一下 ======== 一休割 ===============
那么我们来分析以下为什么会出现文章开头的那个问题,下面一行行分析:
$foo ['love'] = 1; // $foo: refcount=1; isref=0; // ->love: refcount=1; isref=0; $bar = &$foo['love']; // $bar: refcount=2; isref=1; // $foo->love: refcount=2; isref=1; // $foo: refcount=1; isref=0; $tipi = $foo; // $foo: refcount=2; isref=0; // $foo->love: refcount=2; isref=1; // $tipi: refcount=2; isref=0; // $tipi->love: refcount=2; isref=1; // 注意,这一步复合类型(array)$foo的refcount自增到2,而$foo['love']还是数组的hashtable指向的另一块内存地址,它并不会被复制 $tipi['love'] = '2'; // 这里的$tipi['love']是一个引用,如同$foo['love']一样 echo $foo['love']; // 所以当$tipi['love']改变以后,这里自然会输出 2
理解了吗?
相信你看完这个分析后也会认为这个是PHP的特色,我也是这么想的。知道真相后似乎要推翻之前的结论——这是个BUG。但是仔细想想,这种坑实际上是不应该出现的,所以我还是坚持最开始的想法——这就是个BUG!当然,见仁见智。
引用不要滥用,因为PHP本身已经对变量做了很好的优化。但是有些时候还是该用,比如你实际上想传址而不是传值。
另外,大蛇要提醒一句,在5.4.0中,动态引用已经被取消了,例如:
function myfunc($var){ $var = 1; } myfunc(& $foo)
function myfunc(& $var){ $var = 1; } myfunc($foo)
原文地址:从PHP的引用BUG谈开去, 感谢原作者分享。