一个引用造成的血案, $a = &$b

6,356 views

这几天在群里面有人讨论怎样才算PHP基础好, 然后某人给出了一道引用题,是这样的:

$a = 1;

$b = &$a;

echo (++$a) + (++$a);

问,输出为什么是6?(我觉得,好恐怖的基础。如果是这样来判断基础好不好, 那么换成奇葩的JS,估计很多人要剖腹了……)

这是一道有意思的题目, 如果只是通过PHP表面去解释,一般也只能说到因为$a的值是指针地址这个点上。相信说完, 隐约知道是引用, 知道应该是同一个地址的值,但是还是有很多人云里雾里想不透彻吧。

如果通过内核来分析会更清晰一点,我给出我自己的理解。

首先,我们要明白php变量是怎么实现的。有很多文章已经说明了原理,我就不一一细说了,给出学习地址 “PHP变量”  , 接着, 还要明白PHP里引用计数的知识, 传送门 “PHP引用计数” 。 现在我认为你们都有基础的认识了, 然后我们来说下刚才的题目。

在这个PHP脚本被ZEND编译成opcode的时候, OPCODE的代码是这样的, 先看图:

Screenshot from 2014-04-01 23:55:21

 

这段OPCODE,先和鸟哥普及下知识吧, 地址是”深入原理之Opcodes” 。

我们来分析下步骤

首先,$a = 1; 的时候, 是!0, 1, 因为!0代表$a的cache地址, 这时候常量1赋值给$a, 对应的,$a 对应的zval结构中, refcount = 1,  is_ref = 0  。

到$b = &$a;的时候, 是引用赋值, 这时候,变量 $b和$a都指向同一个zval结构, 这个时候zval结构的refcount = 2, is_ref = 1。

到echo (++$a) + (++$a);了, 由图里面可以看出来, 先执行了两次PRE_INC, 就是两次前缀自增。由前面鸟哥说明的OPCODES里面操作数类型区别,显然,$2 !0和$3 !0分别可以看成$a自增后分别返回了2个新的变量IS_VAR(分别是$2, $3), 因为$a对应的zval结构中,refcount = 2, is_ref = 1, 所以新变量$2,$3没有分离, 他们都还同时指向同一个zval 。(关于为什么没有分离的知识,请查阅PHP写时复制和写时分离的相关知识吧,这里就不给出来了)

最后执行ADD指令的时候, 因为是同一个zval相加, 而当前zval因为自增了两次, 变成3, 所以就有了 3+3 = 6

整个过程变成PHP过程大概可以这么理解

$a = 1;

$b = &$a;

$c = &$a;

$d = &$a;

++$c;//或者是 $c = ++$a;

++$d;//或者是 $d = ++$a;

echo $c + $d;

其中$b = &$a 的意义只是为了把$a对应的zval结构中is_ref设置为1罢了。 如果没有了$b = &$a;这句,就是输出5了, 现在大家能想明白为什么是5了吧?

###########

最近开了个技术群, 有兴趣的朋友进来吹牛逼吧, QQ群号221073018

3 thoughts on “一个引用造成的血案, $a = &$b”

    1. 假设$a对应的zval地址在x001, 当$a自增的时候,x001的值是2, $a再自增, x001的值是3, 这个时候 x001 + x001, 不就是3 + 3么

Leave a Reply

Your email address will not be published.