这是我的代码:
$a = 5;
$b = &$a;
echo ++$a.$b++;
不应该打印66?
为什么打印76?
答案 0 :(得分:66)
好的。这实际上是非常简单的行为,它与PHP中的引用如何工作有关。这不是一个错误,而是意外的行为。
PHP内部使用copy-on-write。这意味着在写入内部变量时会复制它们(因此$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
前两个应该很容易理解。
5
的值分配给名为!0
的已编译变量。!0
到!1
的引用(方向无关紧要)到目前为止,这是直截了当的。现在来了有趣的一点:
$2
的临时变量。因此,在使用变量调用时,请查看the source code behind 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语句检查变量是否“安全”递增(第一个检查是否是一个重载对象,第二个检查变量是否是特殊错误变量$php_error
)。
接下来对我们来说真的很有趣。由于我们正在修改该值,因此需要预先写入copy-copy。所以它叫:
SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
现在,请记住,我们已经将变量设置为上面的参考。所以变量没有分开......这意味着我们在这里所做的一切都将发生在$b
......
接下来,变量递增(fast_increment_function()
)。
最后,它将结果设置为。这是再次写入时的复制。它没有返回操作的值,而是返回实际的变量。那么PRE_INC
返回的是仍然是对$a
和$b
的引用。
PRE_INC
类似。让我们再次查看the source code:
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);
}
这次我删除了所有非有趣的东西。那么让我们来看看它在做什么。
首先,它获取上面代码中的返回临时变量(~3
)。
然后将其参数(!1
或$b
)中的值复制到结果中(因此引用被破坏)。
然后它会增加参数。
现在请记住,参数!1
是变量$b
,它引用了!0
($a
)和 {{1如果你还记得那是$2
的结果。
所以你有它。它返回76,因为引用保留在PRE_INC的结果中。
我们可以通过强制复制来证明这一点,首先将pre-inc分配给临时变量(通过正常分配,这将破坏引用):
PRE_INC
哪个像你期望的那样有效。 Proof
我们可以通过引入维护引用的函数来重现其他行为(您的错误):
$a = 5;
$b = &$a;
$c = ++$a;
$d = $b++;
echo $c.$d;
当你看到它时它起作用(76):Proof
注意:这里单独函数的唯一原因是PHP的解析器不喜欢function &pre_inc(&$a) {
return ++$a;
}
$a = 5;
$b = &$a;
$c = &pre_inc($a);
$d = $b++;
echo $c.$d;
。所以我们需要通过函数调用添加一个间接级别来做...
我不认为这是一个错误的原因是它是如何工作的引用。预先递增引用的变量将返回该变量。即使是非引用变量也应该返回该变量。它可能不是你在这里所期望的,但它在几乎所有其他情况下都能很好地运作......
如果您正在使用引用,那么大约99%的时间都是错误的。因此,除非绝对需要它们,否则不要使用引用。 PHP在内存优化方面比你想象的要聪明得多。您对引用的使用确实阻碍了它的工作方式。因此,虽然您认为自己可能正在编写智能代码,但在绝大多数情况下,您真的会编写效率较低且不太友好的代码......
如果您想了解更多有关参考资料以及变量如何在PHP中运行的信息,请查看有关该主题的One Of My YouTube Videos ...
答案 1 :(得分:0)
我认为首先执行完整的连接行,然后使用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
答案 2 :(得分:0)
编辑:添加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
这意味着代码的相同块(第1个和第3个相同)并不总是返回相同的结果。显然,引用和增量使PHP处于虚假状态。