这种 PHP 按值调用的行为有合理的解释吗?还是 PHP 错误?
Posted
技术标签:
【中文标题】这种 PHP 按值调用的行为有合理的解释吗?还是 PHP 错误?【英文标题】:Is there a rational explanation for this PHP call-by-value behavior? Or PHP bug? 【发布时间】:2014-09-06 12:28:14 【问题描述】:php 5.5.12。考虑一下:
<?php
$a = [ 'a', 'b', 'c' ];
foreach($a as &$x)
$x .= 'q';
print_r($a);
正如预期的那样,输出:
Array
(
[0] => aq
[1] => bq
[2] => cq
)
现在考虑:
<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x)
$x .= 'q';
print_r($a);
function z($a)
return $a;
这个输出:
Array
(
[0] => aq
[1] => bq
[2] => cq
)
(!) 但是等一下。 $a 不是通过引用传递的。这意味着我应该从 z() 中获取一个副本,该副本将被修改,并且应该不理会 $a。
但是当我们强制 PHP 执行它的写时复制魔法时会发生什么:
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x)
$x .= 'q';
print_r($a);
function z($a)
$a[0] .= 'x';
return $a;
为此,我们得到了我的期望:
Array
(
[0] => a
[1] => b
[2] => c
)
编辑:再举一个例子……
$a = [ 'a', 'b', 'c' ];
$b = z($a);
foreach($b as &$x)
$x .= 'q';
print_r($a);
function z($a)
return $a;
这按预期工作:
Array
(
[0] => a
[1] => b
[2] => c
)
对此有合理的解释吗?
【问题讨论】:
在我看来$a
is 在示例 #2 中通过引用传递。这确实与 PHP 5.4 ~ eval.in/172839 中发生的情况不同。可能与 5.5 中引入的数组取消引用更改有关
您的最后一个示例无效。 $b = z($a);
立即应用于整个数组。
来自these results 很明显,您实际上不应该依赖这种行为;事实上,这可能是一个错误。
再给你一个古怪的例子~eval.in/172857
@Nairebis 刚刚与另一位 php-src 开发人员快速聊天,他同意这是一个错误。
【参考方案1】:
在这个例子中,函数 z 什么都不做。它不会复制或克隆任何内容,因此来自 z() 的响应将与根本不调用相同。您只是返回传入的对象,因此响应符合预期。
<?php
$a = [ 'a', 'b', 'c' ];
foreach(z($a) as &$x)
$x .= 'q';
print_r($a);
function z($a)
return $a;
Thiis 使用对象更容易演示,因为它们被赋予了系统 ID:
<?php
$obj = new stdClass();
$obj->name = 'foo';
function z($a)
$a->name = 'bar';
return $a;
var_dump($obj);
var_dump(z($obj));
这个的输出是:
object(stdClass)#1 (1)
["name"]=>
string(3) "foo"
object(stdClass)#1 (1)
["name"]=>
string(3) "bar"
两个对象的 ID 均为“1”,表明它们不是副本或克隆。
【讨论】:
但是在 PHP 中,对象是通过引用隐式传递的,而数组不是(意味着)所以你的例子不准确。不过,您可能会对 “函数 z 什么都不做” 有所了解。可能只是工作中的编译器优化 @Phil 我同意 Phil 的观点,即观察到的行为可能是优化的结果,恕我直言,不应该以这种方式工作。 您正在传递和返回对对象的引用,而不是对象本身。引用确实被复制了。【参考方案2】:更新
已打开错误67633 以解决此问题。 this commit 已更改此行为,以消除 foreach 的引用限制。
从this 3v4l output 你可以清楚地看到这种行为随着时间的推移发生了变化:
更新 2
固定为this commit;这将在 5.5.18 和 5.6.2 中可用。
PHP 5.4
在 PHP 5.5 之前,您的代码实际上会引发致命错误:
Fatal error: Cannot create references to elements of a temporary array expression
PHP 5.5 - 5.6
当函数结果直接在foreach
块内使用时,这些版本不执行写时复制。因此,现在使用原始数组,并且对元素的更改是永久性的。
我个人觉得这是个bug;写时复制应该已经发生了。
PHP > 5.6
在phpng branch 中,可能会成为下一个主要版本的基础,常量数组是不可变的,因此只有在这种情况下才能正确执行写时复制。像下面这样声明数组会出现与 phpng 相同的问题:
$foo = 'b';
$a = ['a', $foo, 'b'];
Proof
破解 (HHVM)
只有 Hack 能正确处理目前的情况。
正确的方法
documented通过引用使用函数结果的方式是这样的:
$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;
Demo
其他修复
为避免更改原始数组,目前您有以下选择:
-
将函数结果赋值给一个临时变量之前
foreach
;
不要使用引用;
切换到 Hack。
【讨论】:
确实,根据手册,这也不应该工作~eval.in/172864 我已经确认这已在 5.5.19 中修复(ubuntu 14.04 64bit via ppa:ondrej/php5)以上是关于这种 PHP 按值调用的行为有合理的解释吗?还是 PHP 错误?的主要内容,如果未能解决你的问题,请参考以下文章