php变量分变量名符号和变量内容,变量符号存在符号表中,而变量内容存储在zval变量容器,具体介绍:php
1,zval结构算法
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; zend_ast *ast; } zvalue_value; struct _zval_struct { zvalue_value value; /*描述变量的值*/ zend_uint refcount__gc; zend_uchar type; /*描述变量的类型*/ zend_uchar is_ref__gc; };
_zval_strust包含type和value,另外包含两个字节的额外信息,is_ref:bool值,标示这个变量是不是属于引用集合(reference set),php引擎经过is_ref才能把普通变量和引用变量区分开来。refcount:标示指向这个zval变量容器的变量(也称符号即symbol)个数,用来优化内存使用。数组
符号表:数据结构
全部的变量符号存在一个符号表中,符号有本身的做用域,好比每一个函数或方法域,主脚本域。ide
普通变量类型:函数
当一个变量被赋常量值时,就会生成一个zval变量容器,Example #1性能
<?php $a = "new string"; // 生成一个新的zval容器 xdebug_debug_zval('a'); //结果:a: (refcount=1, is_ref=0),string 'new string' (length=11) $c = $b = $a; // 把一个变量赋值给另外一变量,增长一个引用计数 xdebug_debug_zval('a'); //结果:a: (refcount=3, is_ref=0),string 'new string' (length=11) $b = '123'; xdebug_debug_zval('a'); //结果:a: (refcount=2, is_ref=0),string 'new string' (length=11) unset($c); //变量调用了函数 unset()时,”refcount“就会减1 xdebug_debug_zval('a'); //结果:a: (refcount=1, is_ref=0),string 'new string' (length=11) $d = &$a; xdebug_debug_zval('a'); //结果:a: (refcount=2, is_ref=1),string 'new string' (length=11) unset($a); //包含类型和值的这个变量容器就会从内存中删除。 xdebug_debug_zval('a'); //结果:a: no such symbol ?>
定义了变量a,在当前域中生成类型为string,值位new string的变量容器,此时没有任何自定义引用,is_ref默认为0,只有一个变量在使用,refcount默认是1,安装Xdebug,调用函数 xdebug_debug_zval()显示"refcount"和"is_ref"的值。测试
b变量=a,同一个变量容器被变量a和变量b关联,php不会复制已生成的变量容器,refcount变成2,unset变量时,refcount减一、当任何关联到某个变量容器的变量离开它的做用域(好比:函数执行结束)也会减1,当refcount变成0时就被销毁。优化
复合类型(Compound Types)ui
array和object符合类型,与普通类型不一样,符合类型把成员和属性存在本身的符号表,会有多个zval变量容器
Example #2
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); //Creating a array zval xdebug_debug_zval( 'a' ); /* 结果: a:(refcount=1, is_ref=0), array (size=2) 'meaning' => (refcount=1, is_ref=0),string 'life' (length=4) 'number' => (refcount=1, is_ref=0),int 42 */ ?>
这三个zval变量容器是: a,meaning和 number。增长和减小”refcount”的规则和上面提到的同样
Example #3 添加一个已经存在的元素到数组中
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); $a['life'] = $a['meaning']; xdebug_debug_zval( 'a' ); /*结果: a: (refcount=1, is_ref=0), array (size=3) 'meaning' => (refcount=2, is_ref=0),string 'life' (length=4) 'number' => (refcount=1, is_ref=0),int 42 'life' => (refcount=2, is_ref=0),string 'life' (length=4) */ ?>
原有的数组元素和新添加的数组元素关联到同一个zval变量容器,输出显示两个值为'life'的 zval 变量容器,实际上是同一个。
Example #4 从数组中删除一个元素
<?php $a = array( 'meaning' => 'life', 'number' => 42 ); $a['life'] = $a['meaning']; unset( $a['meaning'], $a['number'] ); xdebug_debug_zval( 'a' ); /*结果: a: (refcount=1, is_ref=0), array (size=1) 'life' => (refcount=1, is_ref=0),string 'life' (length=4) */ ?>
删除数组中的一个元素,就是相似于从做用域中删除一个变量。删除后,数组中的这个元素所在的容器的“refcount”值减小,一样,当“refcount”为0时,这个变量容器就从内存中被删除
如今,当咱们添加一个数组自己做为这个数组的元素时,事情就变得有趣,下个例子将说明这个。例中咱们加入了引用操做符,不然php将生成一个复制。
Example #5 把数组做为一个元素添加到本身
<?php $a = array( 'one' ); $a[] =& $a; xdebug_debug_zval( 'a' ); /*结果: a: (refcount=2, is_ref=1), array (size=2) 0 => (refcount=1, is_ref=0),string 'one' (length=3) '' => (refcount=2, is_ref=1), &array< */ ?>
若是此时unset($a),将删除这个符号,它指向的变量容器引用计数减1,尽管再也不有某个做用域中的任何符号指向这个结构(就是变量容器),因为数组元素“1”仍然指向数组自己,因此这个容器不能被清除,php将在脚本执行结束时清除这个数据结构,可是在php清除以前,将耗费很多内存。
若是你要实现分析算法,或者要作其余像一个子元素指向它的父元素这样的事情,这种状况就会常常发生。固然,一样的状况也会发生在对象上,实际上对象更有可能出现这种状况,由于对象老是隐式的被引用。
若是上面的状况发生仅仅一两次倒没什么,可是若是出现几千次,甚至几十万次的内存泄漏,这显然是个大问题。
引用意味着用不一样的名字访问同一个变量内容,是变量的另一个别名或者映射。引用能够被看做是 Unix 文件系统中的紧密链接或者wins的快捷方式。
PHP 的引用容许用两个变量来指向同一个内容。
<?php $a = " ABC " ; $b = &$a ; echo $a ; // 这里输出:ABC echo $b ; // 这里输出:ABC $b = " EFG " ; echo $a ; // 这里$a的值变为EFG 因此输出EFG echo $b ; // 这里输出EFG ?>
这意味着 $a 和 $b 指向了同一个变量。
注: $a 和$b 在这里是彻底相同的,这并非$a 指向了$b 或者相反,而是$a 和$b 指向了同一个地方。
在PHP5中二者映射到同一对象,不须要使用引用符。
<?php class fooclass{ public $att ; } $a = new fooclass(); $b = $a; $a->att = 1; $b->att = 2; echo 'a obj:',($a->att),'<br>'; echo 'b obj:',($b->att); /** * 结果: * a obj:2 b obj:2 */
其实等效于$b=new fooclass(); $c=&$b;
PHP5中默认就是经过引用来调用对象, 但有时你可能想创建一个对象的副本,并但愿原来的对象的改变不影响到副本 . 为了这样的目的,PHP5定义了一个特殊的方法,称为__clone。
自 PHP 5 起,new 自动返回引用,所以在此使用 =& 已通过时了而且会产生 E_STRICT 级别的消息。
在php4中,对象的赋值是个拷贝过程,
$a = new foo fooclass(), 实际上$a和new fooclass()的映射到不一样对象实例,其中new fooclass产生的是一个匿名的fooclass对象实例 因此须要显式地使用$a = & new fooclass()来进行引用赋值传递。
因此在php4中,为了节省内存空间,$b=new fooclass()通常会改为引用的模式,即 $b=& new fooclass()。
引用传递的定义必须在函数定义中体现,在函数使用中,不要写成foo(&$a)的样子,不然会报"Call-time pass-by-reference过期"的警报。
<?php function foo( & $a ){ $a = $a + 100 ; } $b = 1 ; echo $b ; // 输出1 foo( $b );// 这里$b传递给函数的实际上是$b的变量内容所处的内存地址,经过在函数里改变$a的值 就能够改变$b的值了 echo " <br> " ; echo $b ; // 输出101
可是在函数“call_user_func_array”中,若要引用传参,就得须要 & 符号,以下代码所示:
<?php function test( & $b ){ $b ++ ; } $c = 0 ; call_user_func_array ( 'test' , array ( & $c )); echo $c ;
<? php function & test() { static $b = 0 ; // 申明一个静态变量 $b = $b + 1 ; echo $b ; return $b ; } $a = test(); // 这条语句会输出 $b的值 为1 $a = 5 ; $a = test(); // 这条语句会输出 $b的值 为2 $a =& test(); // 这条语句会输出 $b的值 为3 $a = 5 ; $a = test(); // 这条语句会输出 $b的值 为6 ?>
经过这种方式$a=test();获得的其实不是函数的引用返回,这跟普通的函数调用没有区别,只是将函数的值赋给$a而已, 而$a作任何改变 都不会影响到函数中的$b。
而经过$a=&test()方式调用函数呢, 他的做用是 将return $b中的 $b变量的内存地址与$a变量的内存地址 指向了同一个地方
即产生了至关于这样的效果($a=&$b;) 因此改变$a的值 也同时改变了$b的值 因此在执行了
$a=&test();
$a=5;
之后,$b的值变为了5。
注意:
和参数传递不一样,这里必须在两个地方都用 & 符号:定义和使用时都须要加&符号。
& test()指出返回的是一个引用,而不是一般的一个拷贝。
$a =& test()一样也指出 $a 是做为引用的绑定,而不是一般的赋值。
这里是为了让你们理解函数的引用返回才使用静态变量的,其实函数的引用返回多用在对象中:
[php] view plain copy print?
当用 global $var 声明一个变量时实际上创建了一个到全局变量$_GLOBAL的引用。具体能够写成以下语句:
[php] view plain copy print?
把 global $var; 当成是 $var =& $GLOBALS['var']; 的简写。从而将其它引用赋给 $var 只改变了本地变量的引用。
若是全局变量赋值给另外另一个变量$var_test,删除这个$var_test,也不会 unset 全局变量
[php] view plain copy print?
若是在一个函数内部给一个声明为 global 的变量赋于一个引用,该引用只在函数内部可见。能够经过使用 $GLOBALS 数组避免这一点。
[php] view plain copy print?
[php] view plain copy print?
那么实际上就是改变了$var2的映射关系,以前与$_GLOBAL['var2']的映射就断开了,再也没法修改函数外部$var2的内容。此特性也一样做用在用引用传递函数参数的例子中。
另外,须要注意在函数中unset($var2)不会真正释放变量内容,能够看作只是断开了函数内部$var2的映射而已。
7 . 取消引用 unset与=null
当你 unset 一个引用,只是断开了变量名和变量内容之间的绑定。这并不意味着变量内容被销毁了。例如:
[php] view plain copy print?
不会 unset $b,只是 $a。
使用unset($a)与$a=null的结果是不同的。若是该块内存只有$a一个映射,那么unset($a)与$a=null等价,该内存的引用计数变为0,被自动回收;若是该块内存有$a和$b两个映射,那么unset($a)将致使$a=null且$b不变的状况,而$a=null会致使$a=$b=null的状况。
缘由:某变量赋值为null,将致使该变量对应的内存块的引用计数直接置为0,被自动回收。
在一个对象的方法中,$this 永远是调用它的对象的引用。
9. PHP引用使用注意事项
不少人误解Php中的引用跟C当中的指针同样,事实上并不是如此,并且很大差异。C语言中的指针除了在数组传递过程当中不用显式申明外,其余都须要使用*进行定义,而php中对于地址的指向(相似指针)功能不是由用户本身来实现的,是由Zend核心实现的,php中引用采用的是“引用计数、写时拷贝”的原理,(写时复制(Copy-on-Write,也缩写为COW),顾名思义,就是在写入时才真正复制一分内存进行修改。)
就是除非发生写操做,指向同一个地址的变量或者对象是不会被拷贝的,好比下面的代码:
$a = array('a','c'...'n');
$b = $a;
若是程序仅执行到这里,$b和$b是相同的,可是并无像C那样,$a和$b占用不一样的内存空间,而是指向了同一块内存,这就是php和c的差异,并不须要写成$b=&$a才表示$b指向$a的内存,zend就已经帮你实现了引用,而且zend会很是智能的帮你去判断何时该这样处理,何时不应这样处理。
若是在后面继续写以下代码,增长一个函数,经过引用的方式传递参数,并打印输出数组大小。
[php] view plain copy print?
上面的代码中,咱们经过引用把$a数组传入printArray()函数,zend引擎会认为printArray()可能会致使对$a的改变,此时就会自动为$b生产一个$a的数据拷贝,从新申请一块内存进行存储。这就是前面提到的“引用计数、写时拷贝”概念。
直观的理解:$a将使用本身原始的内存空间,而$b,则会使用新开辟的内存空间,而这个空间将使用$a的原始($a或者$b改变以前)内容空间的内容的拷贝,而后作对应的改变。
若是咱们把上面的代码改为下面这样:
[php] view plain copy print?
上面的代码直接传递$a值到printArray()中,此时并不存在引用传递,因此没有出现写时拷贝。
测试:
[php] view plain copy print?
[php] view plain copy print?
测试结果
值传递:0.031868934631348 s
引用传递: 0.047234058380127
结果引用传递性能降低:50%左右。
因此不正确使用引用,性能反而降低。
举例:
若是在函数里面:php的变量是有生命期间的, 一个函数里面申请的变量的生命只限于这个函数内部,当这个函数执行完毕,它分配的内存都会被销毁。
对象new后是直接传递引用的,$obj=new stdclass;obj是对象标识符的拷贝,都指向同一块内存,函数执行完毕后对象标识符消除,可是全局依然存在;$obj=&new stdclass;obj是对象的引用别名,函数执行完后对象消除,obj也就跟着消除了。