Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/video/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
PHP:写时复制和引用赋值在PHP5和PHP7上执行不同的操作_Php_Php Internals - Fatal编程技术网

PHP:写时复制和引用赋值在PHP5和PHP7上执行不同的操作

PHP:写时复制和引用赋值在PHP5和PHP7上执行不同的操作,php,php-internals,Php,Php Internals,我们有一段简单的代码: 1 <?php 2 $i = 2; 3 $j = &$i; 4 echo (++$i) + (++$i); 我认为它在PHP7中的工作方式是正确的。根据操作数是否在任何地方引用,隐式更改运算符的工作方式是不好的 这是完全重写PHP7最好的地方:没有笨拙/错误驱动的开发v4/v5代码可以工作。免责声明:我不是PHP内部专家(还?),所以这都是我的理解,不能保证100%正确或完整。:) 因此,首先,PHP7的行为——我注意到,HHVM也

我们有一段简单的代码:

1    <?php
2    $i = 2;
3    $j = &$i;
4    echo (++$i) + (++$i);

我认为它在PHP7中的工作方式是正确的。根据操作数是否在任何地方引用,隐式更改运算符的工作方式是不好的


这是完全重写PHP7最好的地方:没有笨拙/错误驱动的开发v4/v5代码可以工作。

免责声明:我不是PHP内部专家(还?),所以这都是我的理解,不能保证100%正确或完整。:)

因此,首先,PHP7的行为——我注意到,HHVM也跟着它——似乎是正确的,PHP5在这里有一个bug。这里不应该有额外的按引用赋值行为,因为无论执行顺序如何,对
+$i
的两个调用的结果都不应该相同

操作码看起来不错;至关重要的是,我们有两个临时变量
$2
$3
,用于保存两个增量结果。但不知何故,PHP5表现得就像我们写的那样:

$i = 2;
$i++; $temp1 =& $i;
$i++; $temp2 =& $i;
echo $temp1 + $temp2; 
与此相反:

$i = 2;
$i++; $temp1 = $i;
$i++; $temp2 = $i;
echo $temp1 + $temp2; 
编辑:在PHP内部邮件列表中指出,在一条语句中使用多个修改变量的操作通常被视为“未定义行为”,而
++
则被视为“未定义行为”

因此,PHP5出于实现/优化的原因返回它所做的值是合理的,即使它在逻辑上与对多个语句的合理序列化不一致

(相对较新)包含类似的语言和示例:

除非本规范中明确说明,否则表达式中的操作数相对于彼此的求值顺序未指定。[…](例如,[…]在完整表达式
$j=$i+$i++
中,
$i
的值是旧的还是新的
$i
,未指定。)

可以说,这是一个比“未定义的行为”更弱的说法,因为这意味着它们是按照某种特定的顺序进行评估的,但我们现在开始吹毛求疵了

phpdbg调查(PHP5) 我很好奇,想了解更多关于内部结构的信息,所以使用了一些游戏

无参考资料 用
$j=$i
代替
$j=&$i
运行代码时,我们从共享一个地址的两个变量开始,refcount为2(但没有is_ref标志):

但一旦您预增量,ZVAL就被分离,并且只有一个temp var与$i共享,因此refcount为2:

Address         Refs    Type            Variable
0x7f189f9ecfc8  2       (integer)       $i
0x7f189f859be8  1       (integer)       $j
参考转让 当变量绑定在一起时,它们共享一个地址(refcount为2)和一个by ref标记:

Address         Refs    Type            Variable
0x7f9e04ee7fd0  2       (integer)       &$i
0x7f9e04ee7fd0  2       (integer)       &$j
在预增量之后(但在加法之前),同一地址的refcount为4,表示2个临时变量被引用错误绑定:

Address         Refs    Type            Variable
0x7f9e04ee7fd0  4       (integer)       &$i
0x7f9e04ee7fd0  4       (integer)       &$j
问题的根源 深入了解上的源代码,我们可以找到
ZEND_PRE_INC
opcode的实现:

PHP 5 关键是:

 SEPARATE_ZVAL_IF_NOT_REF(var_ptr);
因此,仅当结果值当前不是引用时,我们才为其创建新的zval。再往下看,我们有:

if (RETURN_VALUE_USED(opline)) {
    PZVAL_LOCK(*var_ptr);
    EX_T(opline->result.var).var.ptr = *var_ptr;
}
因此,如果实际使用减量的返回值,我们需要“锁定”zval,它跟随一系列宏,基本上意味着“增加其refcount”,然后再将其指定为结果

如果我们早些时候创建了一个新的zval,这很好-我们的refcount现在是2,实际变量为1,运算结果为1。但是如果我们决定不这样做,因为我们需要保持一个引用,我们只是增加现有的引用计数,并指向一个可能会再次更改的zval

PHP7 那么PHP7有什么不同呢?好几件事

首先,phpdbg输出相当无聊,因为在PHP7中整数不再是引用计数;相反,引用赋值会创建一个额外指针,指向内存中的同一地址,即实际整数,该指针本身的refcount为1。phpdbg输出如下所示:

Address            Refs    Type      Variable
0x7f175ca660e8     1       integer   &$i
int (2)
0x7f175ca660e8     1       integer   &$j
int (2)
第二,整数有一个特殊的代码路径:

if (EXPECTED(Z_TYPE_P(var_ptr) == IS_LONG)) {
    fast_long_increment_function(var_ptr);
    if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
        ZVAL_COPY_VALUE(EX_VAR(opline->result.var), var_ptr);
    }
    ZEND_VM_NEXT_OPCODE();
}
因此,如果变量是一个整数(
is_LONG
),而不是一个整数的引用(
is_reference
),那么我们可以将它适当地递增。如果需要返回值,可以将其值复制到结果中(
ZVAL\u copy\u value

如果它是一个引用,我们将不会命中该代码,但我们没有将引用绑定在一起,而是有以下两行代码:

ZVAL_DEREF(var_ptr);
SEPARATE_ZVAL_NOREF(var_ptr);
第一行写着“如果它是一个参考,跟随它到它的目标”;这使我们从“对整数的引用”到整数本身。第二种——我认为——是“如果它是被重新计算过的东西,并且有多个引用,那么创建一个副本”;在我们的例子中,这将不起任何作用,因为整数不关心refcounts

现在我们有了一个可以递减的整数,它将影响所有按引用关联,但对refcounted类型不会影响按值关联。最后,如果我们想要增量的返回值,我们再次复制它,而不仅仅是分配它;这一次使用稍微不同的宏,它将在必要时增加新zval的refcount:

ZVAL_COPY(EX_VAR(opline->result.var), var_ptr);

这闻起来像是以前版本的PHP中的一个bug。通过阅读文档,我没有看到一个明确的语句可以保证任何计算顺序,因此代码依赖于未指定的实现细节。说明:
+$i
将首先递增
$i
,然后根据文档返回
$i
。但是,从变量
$i
到整数值的转换(加号操作所必需的)是在第二次增量操作之前还是之后进行的,尚未定义。嗨,@UlrichEckhardt i'更新问题并添加操作码,似乎增量在
ADD
ECHO
之前。我已经向php内部发送了一封邮件,希望那里的人能给我一些线索。我手头没有链接,但我可以
ZVAL_DEREF(var_ptr);
SEPARATE_ZVAL_NOREF(var_ptr);
ZVAL_COPY(EX_VAR(opline->result.var), var_ptr);