zval结构体  一。zval对比 (上图要右键新标签打开才能看清楚) 二。PHP是怎么知道zval存储了什么类型的变量 三。整型和浮点型存储 四。复杂类型存储 五。引用类型 六。需要解决的疑问 七。参考

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

  PHP的变量是由zval来存储的,PHP7之前的zval主要由value和type组成,后面增加了gc用来垃圾回收以及ref_gc来标志引用类型,共占了24字节,而在通过结构映射扩充zval来解决循环引用的问题,此时一个变量占了32字节,在扩充了zval之后,因为整型和浮点型不需要进行gc,所以整型和浮点型存在内存的浪费(存在有不需要的内存gc),而在开启zend内存池的情况下,一个变量的大小达到了48字节。

  PHP7以后重构了zval,不仅解决了以前的问题,而且内存占用非常小。在PHP7以后,zval支持更丰富的类型,而且不再存储复杂的类型,复杂的类型数据都是通过指针来操作,所以使得zval存储了PHP中的一切,包括整型,字符串,数组,对象等,这些存储全部只占用16字节。

二。PHP是怎么知道zval存储了什么类型的变量

  PHP是弱类型的语言,我们在编程时并不需要指明变量的类型,直接$a等于就行了,但是在底层不知道变量的类型是不行的,一个变量的类型就是意味着变量的大小,意味着需要向操作系统申请多少内存,如果不知道变量的大小就不知道需要申请多大的内存。PHP的变量类型是由zval.v.type来决定的,值存储在zval.value中,而在c和编程的中间,PHP帮我们进行了转换,这也是为什么PHP是用c语言来写的,却不适合用在cpu密集型的场合的最重要的原因之一。过度的向上抽象,使得编程人员不需要过多的关心语言方面,而只需要把时间放在业务上面。

三。整型和浮点型存储

  对于整型和浮点型的存储,因为占用空间小,所以是直接存储的,直接创建两个zval (其他简单类型true/false/double/long/null等类似),然后在zval的value中来获取lval和dval。举例如下:

      创建 int.php

 zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

      进入gdb调试环境

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

        在 echo 所在的行打断点,当然也可以在入口main函数打断点

(gdb) b ZEND_ECHO_SPEC_CV_HANDLER

        运行 int.php 

(gdb) r int.php

       在第一个echo中断,输入 n 往下一步直到获取变量的值

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

        打印一下变量是一个指针

 zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

        打印指针指向的值,这里面就是一个zval

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

       获取变量的类型为4,看图得知为长整型        

 zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

       得知变量类型后,打印value下的长整型的值即为变量存储的值

 zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

        输入 c 继续执行到下一个 echo 断点

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   输入 n 一步一步重复上面的步骤

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   打印变量类型为浮点型

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   查看变量存储的值

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   可见整型和浮点是直接存储而不是指向另一块内存,他们是各自独立一块自己的内存空间来直接存储的。

  接着看一下  代码最后的 unset($a) 

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

  变量的类型是未使用类型,此时并没有真正的释放内存,而是需要时才覆盖或者删除。

四。复杂类型存储

  复杂类型(字符串,数组,对象等)的存储占用空间比较大,所以是共享同一块内存,即同一个zval,在进行某些操作时才会单独分开,比如写时复制等。

五。引用类型

  说到引用类型,就要区别一下传值和传址,引用类型为传址

  传值时,两个变量的地址是不一样的,所以改变一个变量的值时,另一个不会改变。

  传址时,两个变量的地址是一样的,所以改变一个变量的值时,另一个也会一起改变。

  现在来实战一下,以及哪个问题和我们的预期是不一样的

  1. 首先赋值$a

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   2. 接着赋值$b,此时改变$b的值,$a是不会改变的,因为两个变量的地址是不一样的,即两个zval,这里不再演示

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   3. 接着用地址赋值$c

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   4. 接着改变$c的值,我们知道$b也会改变,因为用的是同一个地址,即同一个zval。

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   5. 现在来把$c给删除掉,此时我们的预期$b也会变成空。

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   但是结果$b却还存在,这和我们的预期是不一样的

  问题主要在于,在 $c=&$b时,= 两边的变量类型变成了引用类型

  1. 创建调试代码,调试步骤可看上面

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   2. 首先查看$a的地址为  0x7f1d67c14080 ,类型为6,即字符串(对照上面的图)

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   3. 接着查看$a的值为aaa1577371164,引用计数refcount的值为1 ,@13是查看的长度为13

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

  4. 接着查看$b的地址为0x7f1d67c14090,$a和$b的地址不一样,且相差一个zval的大小, $a 和 $b存储字符串的地址都是0x7f1d67c5e8c0 ,共用这一块内存,复杂类型都是这么共用一块内存的

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   5. 此时$a和$b指向的值的引用计数变为了2

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   6. 在$c = &$b后,看一下$c,地址为0x7f1d67c140a0,类型为10,即引用类型(看上面的图对照)

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   7. 现在看一下$b已经由字符串类型变成了引用类型,而$b和$c的值指向的都是同一块内存 0x7f1d67c01118

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

    8. 现在看一下这地址的值,类型为引用类型,引用计数为2,值的地址为0x7f1d67c5e8c0 ,这个地址和前面$a的值所指向的地址是一样的,也就是说$a,$b,$c的值是存储在同一块内存中的

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   9. 接着再看一下这个地址存储值的值,和前面所看到的值是一样的,即真正存储的值是不变的,此时$a直接指向这个地址,而$b和$c指向了引用的地址,再由这个引用指向这个存储值的地址。

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   10. 接着往下走unset($c),查看一下$c的类型已经变成了0(对照上图),即未使用的类型,此时$c不再被使用而且随时会被覆盖,但$b和$c所指向的引用地址并没有变化,只是把$c的类型变成了不再被使用

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

  11. 此时查看$b的值和之前是没有变化的,依然是指向上面提到的引用地址

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   12. 接着查看引用地址有了什么变化,只是引用计数减少了1,由原来的2变成了1,依然指向了存储值的地址

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   13. 所以得出结论,当使用“&”操作时,会创建一种新的中间结构体zend_reference,这个结构体会指向真正的zend_string结构体,所以zend_string结构体的引用计数不变,同时zend_reference结构体的引用计数变为2,因为$c和$b此时的类型都会变为zend_reference。这样的好处是原始的zend_string在内存中始终只有一份,删除操作也不会影响到其他的值,只会使自身标志为未使用和使中间的引用类型的引用计数减一,如PHP 7底层设计与源码实现这书中的图所示

zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

 zval结构体
 一。zval对比 (上图要右键新标签打开才能看清楚)
二。PHP是怎么知道zval存储了什么类型的变量
三。整型和浮点型存储
四。复杂类型存储
五。引用类型
六。需要解决的疑问
七。参考

   14. 如果在unset之前改变$c的值,$b的值也会改变,$a的值不会改变,这里涉及到写时复制,复制出了另一个zval来存储值。

六。需要解决的疑问

  1.  联合体中为什么需要多加一个没有标识作用的字段?比如 value.u1.type_info 中的type_info以及垃圾回收中的gc.u.type_info等

    value.u1.type_info 标明了答案,主要是联合体中是共用内存的,直接访问type_info就能访问u,而不需要通过u复杂的访问。

  2. 字符串里为什么用柔性数组char val[1],而不是用指针 chat *val ?

    数组是连续的一块内存,访问时只需要一次访存,即获取头地址,然后偏移就行了,用指针需要两次访存,即先获取到指针保存的值,是个地址,再到这个地址去拿值。柔性数组不占用内存,指针会占用。

    C语言中结构体的最后一个元素可以是大小未知的数组,即柔性数组,用来存储不确定长度的数据。

  3. 字符串的柔性数组char val[1]中,为什么不是val[]或者val[0],而偏偏是val[1] ?

    主要是为了兼容不同c编译器,c99以前只支持val[1]这种(这些不重要)

  4. 字符串的长度可以直接计算出来,为什么还需要个len字段来记录字符串的长度?

    一方面是因为二进制安全,可查看 https://www.cnblogs.com/GH-123/p/12159126.html

    另一方面是记录了长度之后,同样的字符串不需要重复的计算,不记录的话同个字符串每次都要重复计算

  5. 已经有了value.u1.type作为变量的类型判断了,垃圾回收的gc为什么还要冗余的多出gc.u.v.type来再次判断变量的类型?

    两个type保存的地址是同一个(可以gdb调试查看),可以看成是个别名,这样可以快速得到值。

七。参考

https://www.amazon.cn/dp/B07D8QSGD9?_encoding=UTF8&psc=1

https://coding.imooc.com/class/312.html