这个PHP按值调用行为是否有合理的解释?还是PHP的bug?

Nai*_*bis 13 php arrays reference pass-by-reference pass-by-value

PHP 5.5.12.考虑一下:

<?php
$a = [ 'a', 'b', 'c' ];
foreach($a as &$x) {
    $x .= 'q';
}
print_r($a);
Run Code Online (Sandbox Code Playgroud)

正如预期的那样,这产生了:

Array
(
    [0] => aq
    [1] => bq
    [2] => cq
)
Run Code Online (Sandbox Code Playgroud)

现在考虑:

<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
    $x .= 'q';
}
print_r($a);

function z($a)
{
    return $a;
}
Run Code Online (Sandbox Code Playgroud)

这输出:

Array
(
    [0] => aq
    [1] => bq
    [2] => cq
)
Run Code Online (Sandbox Code Playgroud)

(!)但是等一下.$ a未通过引用传递.这意味着我应该从z()获得一个副本,该副本将被修改,并且$ a应该被单独留下.

但是当我们强制PHP执行其copy-on-write魔术时会发生什么:

$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
    $x .= 'q';
}
print_r($a);

function z($a)
{
    $a[0] .= 'x';
    return $a;
}
Run Code Online (Sandbox Code Playgroud)

为此,我们得到了我所期望的:

Array
(
    [0] => a
    [1] => b
    [2] => c
)
Run Code Online (Sandbox Code Playgroud)

编辑:还有一个例子......

$a = [ 'a', 'b', 'c' ];
$b = z($a);
foreach($b as &$x) {
    $x .= 'q';
}
print_r($a);

function z($a)
{
    return $a;
}
Run Code Online (Sandbox Code Playgroud)

这按预期工作:

Array
(
    [0] => a
    [1] => b
    [2] => c
)
Run Code Online (Sandbox Code Playgroud)

对此有合理的解释吗?

Ja͢*_*͢ck 9

更新

已打开错误67633以解决此问题.此提交已更改行为,以便从foreach中删除引用限制.


这个3v4l输出中你可以清楚地看到这种行为随着时间的推移而发生了变化:

更新2

修复此提交 ; 这将在5.5.18和5.6.2中提供.

PHP 5.4

在PHP 5.5之前,您的代码实际上会引发致命错误:

Fatal error: Cannot create references to elements of a temporary array expression
Run Code Online (Sandbox Code Playgroud)

PHP 5.5 - 5.6

当函数结果直接在foreach块内使用时,这些版本不执行写时复制.因此,现在使用原始数组,并且对元素的更改是永久性的.

我个人认为这是一个错误 ; 应该发生写时复制.

PHP> 5.6

phpng分支中,它可能成为下一个主要版本的基础,常量数组变为不可变,因此只有在这种情况下才能正确执行写时复制.声明如下所示的数组将与phpng表现出同样的问题:

$foo = 'b';
$a = ['a', $foo, 'b'];
Run Code Online (Sandbox Code Playgroud)

证明

黑客(HHVM)

只有Hack正确处理当前情况.

正确的方式

通过引用使用函数结果的文档化方法是:

$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
    $x .= 'q';
}
print_r($a);

// indicate that this function returns by reference 
// and its argument must be a reference too
function &z(&$a)
{
    return $a;
}
Run Code Online (Sandbox Code Playgroud)

演示

其他修正

为避免更改原始数组,目前,您有以下选项:

  1. 分配的功能的结果到一个临时变量之前foreach;
  2. 不要使用参考;
  3. 切换到Hack.


Tom*_*itt -1

在此示例中,函数 z 不执行任何操作。它不会复制或克隆任何内容,因此 z() 的响应将与根本不调用相同。您只是返回传入的对象,因此响应符合预期。

<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x) {
    $x .= 'q';
}
print_r($a);

function z($a)
{
    return $a;
}
Run Code Online (Sandbox Code Playgroud)

使用对象更容易演示,因为它们被赋予了系统 ID:

<?php
$obj = new stdClass();
$obj->name = 'foo';

function z($a)
{
    $a->name = 'bar';
    return $a;
}

var_dump($obj);
var_dump(z($obj));
Run Code Online (Sandbox Code Playgroud)

其输出是:

object(stdClass)#1 (1) {
  ["name"]=>
  string(3) "foo"
}
object(stdClass)#1 (1) {
  ["name"]=>
  string(3) "bar"
}
Run Code Online (Sandbox Code Playgroud)

两个对象的 ID 均为“1”,这表明它们不是副本或克隆。