php - 递增时的字符串连接

标签 php post-increment pre-increment

这是我的代码:

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

它不应该打印 66 吗?

为什么它打印 76?

最佳答案

好吧。这实际上是非常直接的行为,它与引用在 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 - 我们正在创建来自 !0 的引用至 !1 (方向无所谓)

  • 到目前为止,这是直截了当的。现在是有趣的一点:
  • PRE_INC - 这是实际增加变量的操作码。值得注意的是,它将结果返回到一个名为 $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)。

    接下来是我们真正有趣的部分。由于我们正在修改该值,因此它需要执行写时复制。所以它调用:
    SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
    

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

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

    最后,它设置结果 本身 .这又是写时复制。它不会返回 操作,但实际变量 .那又怎样PRE_INC返回是 仍然是对 $a 的引用和 $b .
  • POST_INC - 这与 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 ) $2 ,如果你还记得的话,这是来自 PRE_INC 的结果.

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

    我们可以通过强制复制来证明这一点,首先将 pre-inc 分配给一个临时变量(通过正常分配,这会破坏引用):
    $a = 5;
    $b = &$a;
    $c = ++$a;
    $d = $b++;
    echo $c.$d;
    

    这按您的预期工作。 Proof

    我们可以通过引入一个函数来维护引用来重现其他行为(你的错误):
    function &pre_inc(&$a) {
        return ++$a;
    }
    
    $a = 5;
    $b = &$a;
    $c = &pre_inc($a);
    $d = $b++;
    echo $c.$d;
    

    正如你所看到的那样有效(76):Proof

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

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

    基础点

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

    如果您想了解更多关于引用以及变量在 PHP 中的工作原理,请查看 One Of My YouTube Videos就此主题而言...

    关于php - 递增时的字符串连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17188279/

    相关文章:

    php - 如何替换 http ://or www with <a href. 。在 PHP 中

    php - 错误 (150) - 设计基本关系数据库时遇到问题

    Javascript 文件在 Wordpress 中无法正常运行,并且在 Functions.php 中进行了正确的排队

    c++ - C++ 中的前置/后置增量指针

    java - C和JAVA中自增自减运算符的区别

    php - MySQL MATCH,AGAINST 不适用于 PDO

    c++ - 左值和右值分配错误

    可以在函数调用的参数中使用后增量运算符吗?在C?

    java - C++ 和 Java 的表达式求值顺序有何不同?

    c++ - 数组 C++ 中的增量运算符