38

这是我的代码:

$a = 5;
$b = &$a;
echo ++$a.$b++;

它不应该打印 66 吗?

为什么会打印 76?

4

3 回答 3

66

好吧。这实际上是非常直接的行为,它与引用在 PHP 中的工作方式有关。这不是错误,而是意外行为。

PHP 在内部使用写时复制。这意味着当您写入内部变量时会复制内部变量(因此$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中。
  • ASSIGN_REF - 我们正在创建从!0to的引用!1(方向无关紧要)

到目前为止,这是直截了当的。现在来了有趣的一点:

  • PRE_INC - 这是实际增加变量的操作码。值得注意的是,它将其结果返回到一个名为 的临时变量$2中。

那么让我们看一下使用变量调用时背后的源代码: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)。

接下来是对我们来说真正有趣的一点。由于我们正在修改该值,因此它需要执行写时复制。所以它调用:

SEPARATE_ZVAL_IF_NOT_REF(var_ptr);

现在,请记住,我们已经将变量设置为上面的引用。所以变量没有分开......这意味着我们在这里所做的一切都会发生$b......

接下来,变量递增 ( fast_increment_function())。

最后,它将结果设置为自身。这又是写时复制。它不是返回操作的,而是实际的变量。所以PRE_INC返回的仍然是对$aand$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);
}

这次我删掉了所有无趣的东西。那么让我们看看它在做什么。

首先,它获取返回的临时变量(~3在我们上面的代码中)。

然后它将其参数 ( or ) 中的值复制到结果中(因此引用被破坏)。!1$b

然后它增加参数。

现在请记住,参数!1是变量$b,它具有对!0( $a) $2的引用,如果您记得的话,它是 的结果PRE_INC

所以你有它。它返回 76,因为引用保留在 PRE_INC 的结果中。

我们可以通过强制复制来证明这一点,首先将 pre-inc 分配给一个临时变量(通过正常分配,这将破坏引用):

$a = 5;
$b = &$a;
$c = ++$a;
$d = $b++;
echo $c.$d;

哪个按您的预期工作。证明

我们可以通过引入一个函数来维护引用来重现其他行为(你的错误):

function &pre_inc(&$a) {
    return ++$a;
}

$a = 5;
$b = &$a;
$c = &pre_inc($a);
$d = $b++;
echo $c.$d;

如您所见,哪个有效(76):证明

注意:这里使用单独函数的唯一原因是 PHP 的解析器不喜欢$c = &++$a;. 所以我们需要通过函数调用添加一个间接级别来做到这一点......

我不认为这是一个错误的原因是引用应该如何工作。预递增引用的变量将返回该变量。即使是未引用的变量也应该返回该变量。这可能不是您在这里所期望的,但它在几乎所有其他情况下都非常有效......

基本点

如果你使用引用,那么大约 99% 的时间你都做错了。所以不要使用引用,除非你绝对需要它们。PHP 在内存优化方面比您想象的要聪明得多。而且您对引用的使用确实阻碍了它的工作方式。因此,尽管您认为自己可能正在编写智能代码,但实际上大多数时候您编写的代码效率较低且不太友好......

如果您想了解更多关于参考和变量如何在 PHP 中工作的信息,请查看有关该主题的我的 YouTube 视频之一...

于 2013-06-19T12:05:24.353 回答
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
于 2013-06-19T10:07:41.517 回答
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

这意味着相同的代码块(第一个和第三个相同)并不总是返回相同的结果。显然,引用和增量将 PHP 置于虚假状态。

https://eval.in/34023

于 2013-06-19T10:28:53.813 回答