本章主要内容来源于秦朋先生编著的《PHP7内核剖析》这本书籍,感兴趣的小伙伴可以购买该书,进行系统性学习。

概述


引用并不是一种独立的类型,而是一种指向其它数据类型的结构,类似于C语言中指针的概念。当修改引用类型的变量时,其修改将反映到实际引用的变量上。

在PHP中通过&操作符生成一个引用变量,比如$a=&$b。

变量实现


  1. struct _zend_reference {
  2. zend_refcounted_h gc;
  3. zval val;
  4. zend_property_info_source_list sources;
  5. };

举个例子:

  1. $a = date("Y-m-d");
  2. $b = &$a;

$a在date()函数返回后被赋值为字符串,此时内存空间数据结构是这样的: 引用的实现 - 图1 在通过&$a将其转为引用类型并赋值给了另外一个变量$b,转换后的$a的类型已经不再是IS_STRING,而变为IS_REFERENCE,$a的value也转变为zend_reference结构,这个结构的val.value指向了原来的字符串。最终的结果:$a、$b指向zend_reference,其引用计数为2,然后zend_reference指向原zend_string,其引用计数为1,也就是$a、$b间接的指向了实际的value。这时内存空间数据结构是这样的: 引用的实现 - 图2 此时,如果再将$a赋值给$c,如下操作:

  1. $c = $a

因为未通过&操作符赋值,所以并不构成引用操作,$c的值将是实际的value,而不是引用,内存空间数据结构如下: 引用的实现 - 图3 以上就是引用的底层实现原理。
在PHP5中,引用只是一个标志位,但是PHP7将引用变成了一种新的类型:IS_REFERNCE,如此设计的原因,鸟哥在其博客中略有提及,感兴趣的朋友可以浏览一下:风雪之隅

整体思想如下:
Hashtabe直接存储的是zval,这样在符号表中,两个zval如何共用一个数值便是一个问题,类似于字符串等复杂类型的变量来说,还好解决,直接在zend_refcounted结构中加一个标志位来表明是引用这种方式便可解决,但也会有Change On Write引起复制操作而带来的性能问题。

然而PHP7中还有一部分变量类型是直接存储在zval中的,比如IS_LONG,引用类型需要引用计数,对于一个是IS_LONG并且又是IS_REFERENCE的zval该如何表示?这就有了zend_reference类型。

同时PHP7中的引用类型还避免了字符串等复杂类型因Change On Write引起复制而带来的性能问题,因为压根就不会复制。