string串联时递增
这是我的代码:
$a = 5; $b = &$a; echo ++$a.$b++;
不应该打印66?
为什么打印76?
好的。 这实际上是非常直接的行为,它与PHP中引用的工作方式有关。 这不是一个错误,而是意想不到的行为。
PHP在内部使用copy-on-write。 这意味着内部variables在你写入时被复制(所以$a = $b;
直到你真正改变它们之一才复制内存)。 有了引用,它从来没有真正的复制。 这对以后很重要。
我们来看看这些操作码:
line # * op fetch ext return operands --------------------------------------------------------------------------------- 2 0 > ASSIGN !0, 5 3 1 ASSIGN_REF !1, !0 4 2 PRE_INC $2 !0 3 POST_INC ~3 !1 4 CONCAT ~4 $2, ~3 5 ECHO ~4 6 > RETURN 1
前两个应该很容易理解。
- ASSIGN – 基本上,我们将5的值赋给名为
!0
的编译variables。 - ASSIGN_REF – 我们正在创build一个从
!0
到!1
的参考(方向无关紧要)
到目前为止,这是直截了当的。 现在来了有趣的一点:
- PRE_INC – 这是实际增加variables的操作码。 值得注意的是,它将其结果返回到名为
$2
的临时variables。
所以让我们看一下使用variables调用PRE_INC
后面的源代码 :
static int ZEND_FASTCALL ZEND_PRE_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE zend_free_op free_op1; zval **var_ptr; SAVE_OPLINE(); var_ptr = _get_zval_ptr_ptr_var(opline->op1.var, execute_data, &free_op1 TSRMLS_CC); if (IS_VAR == IS_VAR && UNEXPECTED(var_ptr == NULL)) { zend_error_noreturn(E_ERROR, "Cannot increment/decrement overloaded objects nor string offsets"); } if (IS_VAR == IS_VAR && UNEXPECTED(*var_ptr == &EG(error_zval))) { if (RETURN_VALUE_USED(opline)) { PZVAL_LOCK(&EG(uninitialized_zval)); AI_SET_PTR(&EX_T(opline->result.var), &EG(uninitialized_zval)); } if (free_op1.var) {zval_ptr_dtor(&free_op1.var);}; CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); } SEPARATE_ZVAL_IF_NOT_REF(var_ptr); if (UNEXPECTED(Z_TYPE_PP(var_ptr) == IS_OBJECT) && Z_OBJ_HANDLER_PP(var_ptr, get) && Z_OBJ_HANDLER_PP(var_ptr, set)) { /* proxy object */ zval *val = Z_OBJ_HANDLER_PP(var_ptr, get)(*var_ptr TSRMLS_CC); Z_ADDREF_P(val); fast_increment_function(val); Z_OBJ_HANDLER_PP(var_ptr, set)(var_ptr, val TSRMLS_CC); zval_ptr_dtor(&val); } else { fast_increment_function(*var_ptr); } if (RETURN_VALUE_USED(opline)) { PZVAL_LOCK(*var_ptr); AI_SET_PTR(&EX_T(opline->result.var), *var_ptr); } if (free_op1.var) {zval_ptr_dtor(&free_op1.var);}; CHECK_EXCEPTION(); ZEND_VM_NEXT_OPCODE(); }
现在我不指望你了解马上做了什么(这是一个深刻的引擎巫术),但让我们通过它。
前两个if语句检查variables是否“安全”增量(第一个检查是否是一个重载对象,第二个检查variables是否是特殊错误variables$php_error
)。
接下来是我们真正有趣的一点。 由于我们正在修改该值,因此需要进行写入时复制。 所以它叫:
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
现在,请记住,我们已经将variables设置为上面的参考。 所以variables不是分开的…这意味着我们在这里做的所有事情都会发生在$b
以及…
接下来,variables增加( fast_increment_function()
)。
最后,它将结果设置为自己 。 这是再次写入。 它不是返回操作的值 ,而是实际的variables 。 那么PRE_INC
返回的是一个对$a
和$b
的引用 。
- POST_INC – 除了一个非常重要的事实外,其行为与
PRE_INC
类似。
让我们再看看源代码 :
static int ZEND_FASTCALL ZEND_POST_INC_SPEC_VAR_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { retval = &EX_T(opline->result.var).tmp_var; ZVAL_COPY_VALUE(retval, *var_ptr); zendi_zval_copy_ctor(*retval); SEPARATE_ZVAL_IF_NOT_REF(var_ptr); fast_increment_function(*var_ptr); }
这一次,我把所有不感兴趣的东西都删掉了。 那么让我们来看看它在做什么。
首先,它得到返回临时variables(在我们的代码中〜3)。
然后它将参数( !1
或$b
)中的值复制到结果中(因此参考被打破)。
然后它增加了这个论点。
现在请记住,参数!1
是variables$b
,它有一个对!0
( $a
) 和 $2
的引用,如果你还记得是PRE_INC
的结果。
所以你有它。 它返回76,因为引用保持在PRE_INC的结果中。
我们可以通过强制一个副本来certificate这一点,方法是先将pre-inc赋值给一个临时variables(通过正常的赋值,这会破坏引用):
$a = 5; $b = &$a; $c = ++$a; $d = $b++; echo $c.$d;
哪个按预期工作。 certificate
我们可以通过引入一个函数来维护引用来重现其他行为(你的bug):
function &pre_inc(&$a) { return ++$a; } $a = 5; $b = &$a; $c = &pre_inc($a); $d = $b++; echo $c.$d;
你看到它的作品(76): certificate
注意:这里单独的函数的唯一原因是PHP的parsing器不喜欢$c = &++$a;
。 所以我们需要通过函数调用添加一个间接级别来完成它…
我不认为这是一个错误的原因是它是如何引用应该工作。 预先递增引用的variables将返回该variables。 即使是一个非引用的variables也应该返回这个variables。 这可能不是你在这里所期望的,但是在几乎所有其他情况下都可以工作得很好。
基础点
如果你使用的是引用,大约99%的时间你做错了。 所以不要使用引用,除非你绝对需要它们。 在内存优化方面,PHP比你想像的要聪明得多。 而你使用引用确实妨碍了它如何工作。 所以,当你认为你可能正在写智能代码时,绝大多数时间你都会写更低效率,更不友好的代码。
如果您想了解更多关于参考资料以及variables在PHP中的工作方式,请查看关于此主题的“我的YouTubevideo之一” …
我认为完整的连接线首先被执行,而不是用echo函数发送。 举例来说
$a = 5; $b = &$a; echo ++$a.$b++; // output 76 $a = 5; $b = &$a; echo ++$a; echo $b++; // output 66
编辑:也非常重要,$ B等于7,但回声之前添加:
$a = 5; $b = &$a; echo ++$a.$b++; //76 echo $b; // output 767
编辑:添加Corbin示例: https : //eval.in/34067
PHP中显然有一个错误。 如果你执行这个代码:
<?php { $a = 5; echo ++$a.$a++; } echo "\n"; { $a = 5; $b = &$a; echo ++$a.$b++; } echo "\n"; { $a = 5; echo ++$a.$a++; }
你得到:
66 76 76
这意味着相同的代码块(第一个和第三个代码相同)并不总是返回相同的结果。 显然,参考和增量是把PHP置于一个假的状态。