PHP中的(++i)前缀自增 和 (i++)后缀自增
发布时间:2015-05-27 19:22:49作者:知识屋
<span style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">当我们学第一门语言时,比如大学课程中的C语言程序设计,也许曾经被前缀自增(++i) 和后缀自增 (i++)纠结过。 曾经以为我们懂了:</span><ul style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; margin: 0px; padding-right: 0px; padding-left: 0px; list-style-type: none;"><li style="margin: 0px; padding-top: 4px; clear: both;">i++ :先引用后增加,先在i所在的表达式中使用i的当前值,后让i加1</li><li style="margin: 0px; padding-top: 4px; clear: both;">++i :先增加后引用,让i先加1,然后在i所在的表达式中使用i的新值</li></ul><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">这个表达基本没错,只能说不够精确。在《Expert C Programming》这本书中的附录中,有这样一段说明: ++i表示取i的地址,增加它的内容,然后把值放在寄存器中;i++表示取i的地址,把它的值装入寄存器中,然后增加内存中的i的值。 这里的寄存器存放的就是我们在表达式中使用的值。</p><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">在PHP中也有++$i和$i++,那么Zend内核是如何实现这两种自增方式的呢? 看下面一个例子,在不运行这段代码的情况下,你认为会输出什么呢?</p><pre style="white-space: pre-wrap; word-wrap: break-word; line-height: 26px; background-color: rgb(51, 51, 51); padding: 10px; overflow: auto; font-size: 13px; font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Courier New', monospace; color: rgb(255, 255, 255);"><span style="color: rgb(109, 156, 190);">$i</span> <span style="color: rgb(224, 136, 47);">=</span> <span style="color: rgb(18, 153, 218);">0</span><span style="color: rgb(224, 136, 47);">;</span><span style="color: rgb(109, 156, 190);">$i</span> <span style="color: rgb(224, 136, 47);">=</span> <span style="color: rgb(109, 156, 190);">$i</span><span style="color: rgb(224, 136, 47);">++;</span><a href="http://www.php.net/echo" style="color: rgb(18, 153, 218); text-decoration: none;" target="_blank"><span style="color: rgb(226, 57, 45);">echo</span></a> <span style="color: rgb(109, 156, 190);">$i</span><span style="color: rgb(224, 136, 47);">;</span></pre><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">咱们先不论答案是什么?我们直接从Zend内核查看这种自增操作的实现。</p><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">使用VLD查看包含了$i++和++$i的PHP代码生成的中间代码:</p><pre style="white-space: pre-wrap; word-wrap: break-word; line-height: 26px; background-color: rgb(51, 51, 51); padding: 10px; overflow: auto; font-size: 13px; font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Courier New', monospace; color: rgb(255, 255, 255);"><span style="color: rgb(109, 156, 190);">$i</span> <span style="color: rgb(224, 136, 47);">=</span> <span style="color: rgb(18, 153, 218);">0</span><span style="color: rgb(224, 136, 47);">;</span><span style="color: rgb(109, 156, 190);">$i</span><span style="color: rgb(224, 136, 47);">++;</span><span style="color: rgb(224, 136, 47);">++</span><span style="color: rgb(109, 156, 190);">$i</span><span style="color: rgb(224, 136, 47);">;</span></pre><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;"><a href="http://www.phppan.com/2011/05/vld-extension/" style="color: rgb(18, 153, 218); text-decoration: none;" target="_blank">使用VLD命令</a>(php -dvld.active=1 -dvld.verbosity=3 t.php)查看详细参数:</p><pre style="white-space: pre-wrap; word-wrap: break-word; line-height: 26px; background-color: rgb(51, 51, 51); padding: 10px; overflow: auto; font-size: 13px; font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Courier New', monospace; color: rgb(255, 255, 255);">number of ops: 8compiled vars: !0 = $iline # * 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: 7path #1: 0,</pre><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">从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函数中有几个关键点:</p><ul style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; margin: 0px; padding-right: 0px; padding-left: 0px; list-style-type: none;"><li style="margin: 0px; padding-top: 4px; clear: both;">CV类型变量的获取,它是调用_get_zval_ptr_ptr_cv获取CV类型变量。 这里的CV类型的变量是PHP编译期间的类似于缓存的作用,主要作用是提高某些变量的存储速度。</li><li style="margin: 0px; padding-top: 4px; clear: both;">increment_function函数,不管是实例变量,类变量或者常规的变量,最终都是调用increment_function函数实现变量的增加操作。 在这个函数中,程序会根据变量的类型做出不同的处理,在PHP5.3.1这个版本中,PHP支持IS_LONG、IS_DOUBLE、IS_NULL和IS_STRING四种类型。 如果变量的类型是IS_NULL,程序会将变量的值赋值为1。如果变量类型是字符串,程序会将其转化成整形或浮点型进行计算。</li><li style="margin: 0px; padding-top: 4px; clear: both;">使用RETURN_VALUE_UNUSED宏清除返回结果,这个宏的作用是将result变量的类型设置为EXT_TYPE_UNUSED类型。</li></ul><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">前缀自增(++$i)操作在Zend内核中本质上是操作变量本身,而且在表达式中使用的也是这个变量本身。</p><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">了解了++$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函数增加变量值的过程, 但是除此之外它多了一个操作,同时也少了一个操作。 它多的一个操作是:</p><pre style="white-space: pre-wrap; word-wrap: break-word; line-height: 26px; background-color: rgb(51, 51, 51); padding: 10px; overflow: auto; font-size: 13px; font-family: 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', 'Courier New', monospace; color: rgb(255, 255, 255);">EX_T(opline<span style="color: rgb(224, 136, 47);">-></span>result.u.var).tmp_var <span style="color: rgb(224, 136, 47);">=</span> <span style="color: rgb(224, 136, 47);">**</span>var_ptr<span style="color: rgb(224, 136, 47);">;</span>zendi_zval_copy_ctor(EX_T(opline<span style="color: rgb(224, 136, 47);">-></span>result.u.var).tmp_var)<span style="color: rgb(224, 136, 47);">;</span></pre><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">这两行代码的作用是初始化返回值到临时变量,并且将原始的$i的值存储在这,这就是我们在前面使用VLD查看生成的中间代码其结果为RES[ IS_TMP_VAR ~1 ]的原因。 在这个初始化完成后,程序会继续执行增加操作,在增加操作完成后,它就结束了,而之前的++$i操作则会将result设置为UNUSED类型,这就是它少的那个操作。</p><p style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px; text-indent: 2em;">后缀自增($i++)在表达式中使用的是存放在临时变量中原先的变量值,而变量本身的值已经增加了。 在PHP中这种变量的分离是通过临时变量+返回值解决。</p><div style="color: rgb(54, 46, 43); font-family: Simsun; font-size: 16px; line-height: 24px;">到这里,我们可以回答最开始的问题了,它会输出0。因为在表达式中$i++的返回值是一个临时变量,也就是$i原来的值,也就是0。</div> (免责声明:文章内容如涉及作品内容、版权和其它问题,请及时与我们联系,我们将在第一时间删除内容,文章内容仅供参考)