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置于一个假的状态。

https://eval.in/34023