带有引用参数的 foreach 何时危险?
Posted
技术标签:
【中文标题】带有引用参数的 foreach 何时危险?【英文标题】:When is foreach with a parameter by reference dangerous? 【发布时间】:2018-06-18 19:18:18 【问题描述】:我知道,在 foreach 中通过引用传递项目可能很危险。
特别是,不能重用通过引用传递的变量,因为它会影响$array
,如下例所示:
$array = ['test'];
foreach ($array as &$item)
$item = $item;
$item = 'modified';
var_dump($array);
数组(1) [0]=> &string(8) "修改"
现在这让我很生气:数组的内容在函数should_not_modify
内被修改,即使我没有按值传递$array
。
function should_not_modify($array)
foreach($array as &$item)
$item = 'modified';
$array = ['test'];
foreach ($array as &$item)
$item = (string)$item;
should_not_modify($array);
var_dump($array);
数组(1) [0]=> &string(8) "修改"
我很想检查我的整个代码库并在每个 foreach($array => &$item)
之后插入 unset($item);
。
但是,由于这是一项艰巨的任务,并且引入了一条可能无用的行,我想知道是否有一个简单的规则可以知道 foreach($array => &$item)
之后没有 unset($item);
什么时候是安全的,什么时候不安全。
编辑澄清
我想我明白会发生什么以及为什么。我也知道什么是最好的对抗:foreach($array as &$item)...;unset($item);
foreach($array as &$item)
之后我知道这很危险:
$item
将数组传递给函数
我的问题是:是否还有其他危险的案例,我们能否建立一份详尽的危险清单。或者反过来:是否可以描述什么时候不危险。
【问题讨论】:
“危险”是主观的。有时它是有用的或必要的。 @tadman 是的,就像一把刀:它通常有用且必要,但也很危险。 @poke 你错了,***.com/questions/2030906/…,数组是按值传递的。在我的例子中,数组是按值传递的,但里面的$item
在foreach
之后仍然是一个引用。
按值传递的数组。看那里 - eval.in/932720 你的问题是分配 $item - 当 $item 是参考时,你保存的不是价值,而是参考。注意var_dump中的&
好吧,现在我真的很困惑。这根本不是我认为 php 工作的方式,而且它实际上与引用循环无关。 any 引用存在于函数外部的数组是一个问题:请参阅eval.in/932728
【参考方案1】:
关于foreach
首先,关于 PHP 的两种行为的一些(可能是显而易见的)澄清:
foreach($array as $item)
将在循环后保持变量 $item
不变。如果变量是引用,如foreach($array as &$item)
,即使在循环之后,它也会“指向”数组的最后一个元素。
当变量是引用时,则赋值,例如$item = 'foo';
将更改引用指向的任何内容,而不是变量 ($item
) 本身。 对于后续的 foreach($array2 as $item)
也是如此,如果 $item
是这样创建的,它会将 $item
视为引用,因此将修改引用指向的任何内容(数组的最后一个元素在本例中用于之前的foreach
)。
显然这很容易出错,这就是为什么您应该始终unset
foreach
中使用的引用以确保后续写入不会修改最后一个元素(如类型数组的文档的example #10 )。
关于修改数组的函数
值得注意的是 - 正如@iainn 在评论中指出的那样 - 您示例中的行为与foreach
无关。仅存在对数组元素的引用将允许修改该元素。示例:
function should_not_modify($array)
$array[0] = 'modified';
$array[1] = 'modified2';
$array = ['test', 'test2'];
$item = & $array[0];
should_not_modify($array);
var_dump($array);
将输出:
array(2)
[0] =>
string(8) "modified"
[1] =>
string(5) "test2"
这确实令人惊讶,但explained in the PHP documentation "What References Do"
但是请注意,数组内的引用具有潜在的危险。使用右侧的引用进行普通(非引用)赋值不会将左侧变为引用,但数组内的引用会保留在这些普通赋值中。 这也适用于数组按值传递的函数调用。 [...] 换句话说,数组的引用行为是在逐个元素的基础上定义的;单个元素的引用行为与数组容器的引用状态是分离的。
使用以下示例(复制/粘贴):
/* Assignment of array variables */
$arr = array(1);
$a =& $arr[0]; //$a and $arr[0] are in the same reference set
$arr2 = $arr; //not an assignment-by-reference!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* The contents of $arr are changed even though it's not a reference! */
重要的是要了解在创建引用时,例如$a = &$b
,那么$a
和$b
是相等的。 $a
不指向 $b
,反之亦然。 $a
和 $b
指向同一个地方。
因此,当您执行$item = & $array[0];
时,您实际上使$array[0]
指向与$item
相同的位置。由于$item
是一个全局变量,并且数组内的引用被保留,因此从任何地方(甚至在函数内部)修改$array[0]
都会全局修改它。
结论
还有其他危险的情况吗?我们能否建立一份详尽的危险清单。或者反过来:是否可以描述什么时候不危险。
我将再次重复 PHP 文档中的引用:“数组内的引用具有潜在危险”。
所以不,不可能描述什么时候它不危险,因为它从来没有不危险。很容易忘记 $item
已被创建为引用(或者全局引用已创建但未销毁),并在代码的其他地方重用它并破坏数组。这一直是一个争论的话题(this bug for example),人们称之为错误或功能......
【讨论】:
【参考方案2】:公认的答案是最好的,但我想补充一下:在foreach($array as &$item)
之后什么时候不需要unset($item);
?
$item
: 以后再不重复使用也无妨。
$array
:最后一个元素是引用。这总是很危险的,原因已经说明了。
那么是什么改变了元素形式作为对值的引用?
被引用次数最多:unlink($item);
当数组从函数返回时$item
超出范围,则数组从函数返回后变为“正常”。
function test()
$array = [1];
foreach($array as &$item)
$item = $item;
var_dump($array);
return $array;
$a = test();
var_dump($a);
数组(1) [0]=> &int(1) 数组(1) [0]=> 整数(1)
但要注意:如果您在返回前做任何其他事情,它可能会咬人!
【讨论】:
【参考方案3】:您可以通过“json decode/encode”打破引用
function should_not_modify($array)
$array = json_decode(json_encode($array),false);
foreach($array as &$item)
$item = 'modified';
$array = ['test'];
foreach ($array as &$item)
$item = (string)$item;
should_not_modify($array);
var_dump($array);
这个问题纯粹是学术性的,这有点像黑客。但是,这很有趣,以一种愚蠢的编程方式。
当然它会输出:
array(1)
[0]=>string(4) "test"
另一方面,同样的事情也适用于 javascript,它也可以让你从引用中获得一些奇怪的东西。
我希望我有一个很好的例子,因为我发生了一些“奇怪”的事情,我的意思是一些量子纠缠的事情。这一次在 PHP 训练营中,我有一个递归函数(通过引用传递)和一个 foreach(通过引用传递),它有点像在时空连续体中撕裂了一个洞。
【讨论】:
在 foreach 循环之后使用unset($item);
比 json_decode(json_encode())
好得多,无论是从性能角度还是代码可读性。另外,修改函数是错误的,因为函数应该保持通用。
问题是为什么函数中有一个引用,而不是如何“打破”它。
@iainn - 我完全理解这一点。但是谢谢你提到它。以上是关于带有引用参数的 foreach 何时危险?的主要内容,如果未能解决你的问题,请参考以下文章
python如何决定何时按值传递参数以及何时按引用传递参数? [复制]
从 ForEach 中引用变量时,SwiftUI“无法推断通用参数'数据'”