当我们学第一门语言时,比如大学课程中的C语言程序设计,也许曾经被前缀自增(++i) 和后缀自增 (i++)纠结过。 曾经以为我们懂了:
- i++ :先引用后增加,先在i所在的表达式中使用i的当前值,后让i加1
- ++i :先增加后引用,让i先加1,然后在i所在的表达式中使用i的新值
这个表达基本没错,只能说不够精确。在《Expert C Programming》这本书中的附录中,有这样一段说明: ++i表示取i的地址,增加它的内容,然后把值放在寄存器中;i++表示取i的地址,把它的值装入寄存器中,然后增加内存中的i的值。 这里的寄存器存放的就是我们在表达式中使用的值。
在PHP中也有++$i和$i++,那么Zend内核是如何实现这两种自增方式的呢? 看下面一个例子,在不运行这段代码的情况下,你认为会输出什么呢?
$i = 0; $i = $i++; echo $i;
咱们先不论答案是什么?我们直接从Zend内核查看这种自增操作的实现。
使用VLD查看包含了$i++和++$i的PHP代码生成的中间代码:
$i = 0; $i++; ++$i;
使用VLD命令(php -dvld.active=1 -dvld.verbosity=3 t.php)查看详细参数:
number of ops: 8 compiled vars: !0 = $i line # * op fetch ext return operands -------------------------------------------------------------------------------- - 2 0 > EXT_STMT RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_UNUSED ] 1 ASSIGN OP1[IS_CV !0 ] OP2[ , IS_CONST (0) 0 ] 3 2 EXT_STMT RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_UNUSED ] 3 POST_INC RES[ IS_TMP_VAR ~1 ] OP1[ IS_CV !0 ] 4 FREE OP1[IS_TMP_VAR ~1 ] 4 5 EXT_STMT RES[ IS_UNUSED ] OP1[ IS_UNUSED ] OP2[ IS_UNUSED ] 6 PRE_INC OP1[IS_CV !0 ] 5 7 > RETURN OP1[IS_CONST (0) 1 ] branch: # 0; line: 2- 5; sop: 0; eop: 7 path #1: 0,
从VLD扩展的输出信息可以知道,前缀自增(++$i)对应的opcode为PRE_INC,后缀自增($i++)对应的opcode为POST_INC。 首先我们看前缀自增(++$i),++$i没有返回值或者说它的返回值为空。 根据中间代码和VLD显示的OP1的参数类型, 我们可以知道++$i的中间代码在执行是最终调用的是Zend/zend_vm_execute.h文件中的ZEND_PRE_INC_SPEC_CV_HANDLER函数。 在ZEND_PRE_INC_SPEC_CV_HANDLER函数中有几个关键点:
- CV类型变量的获取,它是调用_get_zval_ptr_ptr_cv获取CV类型变量。 这里的CV类型的变量是PHP编译期间的类似于缓存的作用,主要作用是提高某些变量的存储速度。
- increment_function函数,不管是实例变量,类变量或者常规的变量,最终都是调用increment_function函数实现变量的增加操作。 在这个函数中,程序会根据变量的类型做出不同的处理,在PHP5.3.1这个版本中,PHP支持IS_LONG、IS_DOUBLE、IS_NULL和IS_STRING四种类型。 如果变量的类型是IS_NULL,程序会将变量的值赋值为1。如果变量类型是字符串,程序会将其转化成整形或浮点型进行计算。
- 使用RETURN_VALUE_UNUSED宏清除返回结果,这个宏的作用是将result变量的类型设置为EXT_TYPE_UNUSED类型。
前缀自增(++$i)操作在Zend内核中本质上是操作变量本身,而且在表达式中使用的也是这个变量本身。
了解了++$i的实现,我们来看下可能使用得更多的$i++操作的实现。 同样,从中间代码POST_INC和OP1的类型是IS_CV,我们可以在Zend/zend_vm_execute.h文件中找到其实现为ZEND_POST_INC_SPEC_CV_HANDLER。 与前面的ZEND_PRE_INC_SPEC_CV_HANDLER相比,它们都有一个取CV类型变量的过程,也有一个increment_function函数增加变量值的过程, 但是除此之外它多了一个操作,同时也少了一个操作。 它多的一个操作是:
EX_T(opline->result.u.var).tmp_var = **var_ptr; zendi_zval_copy_ctor(EX_T(opline->result.u.var).tmp_var);
这两行代码的作用是初始化返回值到临时变量,并且将原始的$i的值存储在这,这就是我们在前面使用VLD查看生成的中间代码其结果为RES[ IS_TMP_VAR ~1 ]的原因。 在这个初始化完成后,程序会继续执行增加操作,在增加操作完成后,它就结束了,而之前的++$i操作则会将result设置为UNUSED类型,这就是它少的那个操作。
后缀自增($i++)在表达式中使用的是存放在临时变量中原先的变量值,而变量本身的值已经增加了。 在PHP中这种变量的分离是通过临时变量+返回值解决。
到这里,我们可以回答最开始的问题了,它会输出0。因为在表达式中$i++的返回值是一个临时变量,也就是$i原来的值,也就是0。
Pingback引用通告: Thinking In LAMP Blog » Blog Archive » PHP每月通讯(2011年6月)
哥们,我看了几遍,我才学php几个月,硬是没看明白你说的,前缀自增和后缀自增我的理解很简单,++i是:把i先加1,然后返回值,而i++是先返回值,然后加1,换个角度说他们不同之处:++i的当前值是已经加1的,而i++是当前值还是原来的i,只有在下次用i时才是+1的
欢迎多交流
http://talks.php.net/show/php-best-practices/32
有这么一句:“Where possible use ++$i instead of $i++”,但是不明白为什么这样做,博主上面分析说++$i和$i++操作数貌似是一样的,++$i比$i++效率高?
它们本质的区别是对返回值的处理,++$i返回自己,$i++返回临时变量。在性能上区别不大。
void main()
{
int i = 0;
i = i++;
printf(“%d\n”,i);
}
为什么在C语言中输出的是1
在不同的编译器下这段代码的结果是不一样的。http://blog.csdn.net/hywin1020/article/details/4463619
这篇文章解释i=0的情况,比较详细。
hi,胖胖,请教一个问题:
1.
$a = 10;
$b = &$a;
echo ++$a + $a++;//23
2.
$a = 10;
$b = $a;
echo ++$a + $a++;//22
麻烦请教一下, 这个变量的引用如何影响到最终的输出结果的?
看了一下zval共享和分离的相关资料,没有线索和头绪。
简单的介绍就好弄的那么复杂。
有看没有懂