在看下面的内容之前,先了解一下zval的结构typedefstruct_zval_struct{zvalue_valuevalue;zend_uintrefcount;zend_uchartype;zend_ucharis_ref;}zval;zval结构中有4个元素,value是一个union,用于真正存储zval的值,refcount用于统计zval被使用了多少个变量,type表示zval中存储的数据类型,is_ref用于表示zval是否被引用。引用计数下面我们一起来分析一下上面的代码:$a='HelloWorld';首先执行这段代码,内核创建一个变量并分配12个字节的内存来存储字符串'HelloWorld'和尾随的NULL。$b=$a;然后执行这段代码,执行这句话的时候内核会发生什么?$a指向的zval中的引用计数加1。将变量$b指向$a指向的zval。内核中大概是这样的,active_symbol_table是当前变量符号表{zval*helloval;MAKE_STD_ZVAL(helloval);ZVAL_STRING(helloval,"HelloWorld",1);zend_hash_add(EG(active_symbol_table),"a",sizeof("a"),&helloval,sizeof(zval*),NULL);ZVAL_ADDREF(helloval);zend_hash_add(EG(active_symbol_table),"b",sizeof("b"),&helloval,sizeof(zval*),NULL);}未设置($a);这段代码执行后,内核会将a对应的zval结构体中的refcount计数减1,b会像之前写的那样被复制上面代码执行后,一般希望$a=1,$b=6,但是如果像引用计数一样,$a和$b指向同一个zval,修改$b后$a也不行改变?这个是怎么实现的,一起来看看:$a=1;内核创建一个zval并分配4个字节来存储数字1。$b=$a;这一步与引用计数的第二步相同,将$b指向与$a相同的zval,将zval中的引用计数值refcount加1。$b+=5;关键是这一步,这一步发生了什么,修改后如何保证$a不受影响。事实上,Zend内核会在更改zval之前执行get_var_and_separete操作。如果recfount>1,需要分离出一个新的zval返回。否则直接返回变量指向的zval。让我们看看如何分离并生成一个新的zval。复制一个与$b指向的zval相同的zval。将$b指向的zval中的refcount计数减1。初始化生成的新zval,设置refcount=1,is_ref=0。让$b指向新生成的zval。对新生成的zval进行操作,即copy-on-write。电影电影电影电影的电影的电影的电影的电影:zval*get_var_and_separate(char*varname,intvarname_lenTSRMLS_DC){zval**varval,*varcopy;if(zend_hash_find(EG(active_symbol_table),varname,varname_len+1,(void**))&varval)==FAILURE){/*Variabledoes'tactuallyexistfailout*/returnNULL;}if((*varval)->is_ref||(*varval)->refcount<2){/*varnameistheonlyactualreference,*orit'safullreferencetoothervariables*任一方式:noseseparatingtobedone*/return*varval;}/*否则,复制zval*值*/MAKE_STD_ZVAL(varcopy);varcopy=*varval;/*复制zval内的任何分配结构**/zval_copy_ctor(varcopy);/*删除旧版本的varname*这将减少varvalin进程的ref计数*/zend_hash_del(EG(active_symbol_table),varname,varname_len+1);/*初始化*新创建的值的引用计数并附加到*varname变量*/varcopy->refcount=1;varcopy->is_ref=0;zend_hash_add(EG(active_symbol_table),varname,varname_len+1,&varcopy->is_ref=0);sizeof(zval*),NULL);/*returnthenewzval**/returnvarcopy;}写的时候改上面代码执行完后,一般希望是:$a==$b==1这是如何实现的?$a=1;此步骤与写时复制中的第一步相同。$b=&$a;这一步内核会将$b指向$a所指向的zval,将zval中的refcount加1,并将zval中的is_ref设置为1。$b+=5;这一步和copy-on-write中的第三步是一样的,但是在内核中发生的事情是不一样的。当内核看到$b在变化的时候,也会执行get_var_and_separate函数,看是否需要分开。如果(*varval)->is_ref,则直接返回$b指向的zval,不分离生成新的zval,不管zval的refcount是否>1。这时候如果修改$b的值,$a的值也会改变,因为它们指向同一个zval。分离的问题是,现在你很聪明,你可能已经看到了一些问题。如果一个zval结构同时具有refcount计数和is_ref引用怎么办?如果出现上面的情况,如果$a,$b,$c指向同一个zval结构,改谁做Zend听什么时候说到音乐?其实这个地方不会再指向同一个zval了。如果一个is_ref=0&&refcount>1的zval在写的时候被改变了(也就是引用赋值),Zend会把等号右边的变量分离成一个新的zval,初始化这个zval,以及refcount的之前的zval减1,等号左边的变量指向新的zval,refcount加1,is_ref=1。看下图这是上面的另一种情况,在is_ref=1的情况下,尝试简单的执行当refcount+1操作时,会分离出一个新的zval赋给等号左边的变量,并进行初始化。看看下面的图片。参考1.《Extending and Embedding PHP》-第3章-内存管理。