什么时候带有引用参数的 foreach 是危险的?
When is foreach with a parameter by reference dangerous?
我知道,在 foreach 中通过引用传递项目可能很危险。
特别是,不能重用通过引用传递的变量,因为它会影响 $array
,如本例所示:
$array = ['test'];
foreach ($array as &$item){
$item = $item;
}
$item = 'modified';
var_dump($array);
array(1) {
[0]=>
&string(8) "modified"
}
现在这让我很不爽:数组的内容在函数 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);
array(1) {
[0]=>
&string(8) "modified"
}
我很想遍历我的整个代码库并在每个 foreach($array => &$item)
.
之后插入 unset($item);
但是,由于这是一项艰巨的任务,并且引入了一条可能无用的行,我想知道是否有一个简单的规则可以知道 foreach($array => &$item)
何时安全且后面没有 unset($item);
, 而当不是时。
编辑澄清
我想我明白会发生什么以及为什么。我也知道什么是最好的对抗:foreach($array as &$item){...};unset($item);
我知道在 foreach($array as &$item)
:
之后这很危险
- 重用变量
$item
- 将数组传递给函数
我的问题是:是否还有其他危险情况,我们能否建立一份详尽的危险清单。或者反过来:是否可以描述什么时候不危险。
您可以通过 "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);
这个问题纯粹是学术性的,这有点hack。但是,它以一种愚蠢的编程方式很有趣。
当然它输出:
array(1) {
[0]=>string(4) "test"
}
另一方面,同样的事情在 JavaScript 中起作用,这也可以让您从参考文献中得到一些靠不住的东西。
我希望我有一个很好的例子,因为我遇到了一些 "weird" 事情,我的意思是像一些量子纠缠。这一次在 PHP 营地,我有一个递归函数(通过引用传递)和一个 foreach(通过引用传递)并且它有点在 space 时间连续体中撕开一个洞。
关于foreach
首先,对 PHP 的两个行为进行一些(可能是显而易见的)澄清:
foreach($array as $item)
将在循环后保持变量 $item
不变。如果变量是引用,如 foreach($array as &$item)
,即使在循环之后,它也会 "point" 到数组的最后一个元素。
当一个变量是一个引用然后赋值,例如$item = 'foo';
将更改引用指向的任何内容, 而不是 变量 ($item
) 本身。 对于后续的 foreach($array2 as $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"
Note, however, that references inside arrays are potentially dangerous. Doing a normal (not by reference) assignment with a reference on the right side does not turn the left side into a reference, but references inside arrays are preserved in these normal assignments. This also applies to function calls where the array is passed by value. [...] In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.
以下面的例子(copy/pasted):
/* 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]
都会在全局范围内修改它。
结论
Are there other cases that are dangerous, and can we build an exhaustive list of what is dangerous. Or the other way round: is it possible to describe when it is not dangerous.
我要再次引用 PHP 文档中的话:"references inside arrays are potentially dangerous".
所以不,不可能描述什么时候不危险,因为它永远不危险。很容易忘记 $item
已创建为引用(或已创建但未销毁的全局引用),并在代码的其他地方重用它并破坏数组。这一直是争论的话题(在 this bug for example 中),人们称它为错误或功能...
接受的答案是最好的,但我想补充一下:foreach($array as &$item)
之后什么时候不需要 unset($item);
?
$item
: 以后不重复使用,就不会造成伤害。
$array
:最后一个元素是引用。出于上述所有原因,这总是很危险。
那么是什么改变了元素形式成为对值的引用?
被引用次数最多:unlink($item);
当数组从函数 return 编辑时 $item
超出范围,则数组在 return 之后变为 'normal'来自函数。
function test(){
$array = [1];
foreach($array as &$item){
$item = $item;
}
var_dump($array);
return $array;
}
$a = test();
var_dump($a);
array(1) {
[0]=>
&int(1)
}
array(1) {
[0]=>
int(1)
}
但要注意: 如果你在 return 之前做任何其他事情,它可能会咬人!
我知道,在 foreach 中通过引用传递项目可能很危险。
特别是,不能重用通过引用传递的变量,因为它会影响 $array
,如本例所示:
$array = ['test'];
foreach ($array as &$item){
$item = $item;
}
$item = 'modified';
var_dump($array);
array(1) { [0]=> &string(8) "modified" }
现在这让我很不爽:数组的内容在函数 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);
array(1) { [0]=> &string(8) "modified" }
我很想遍历我的整个代码库并在每个 foreach($array => &$item)
.
unset($item);
但是,由于这是一项艰巨的任务,并且引入了一条可能无用的行,我想知道是否有一个简单的规则可以知道 foreach($array => &$item)
何时安全且后面没有 unset($item);
, 而当不是时。
编辑澄清
我想我明白会发生什么以及为什么。我也知道什么是最好的对抗:foreach($array as &$item){...};unset($item);
我知道在 foreach($array as &$item)
:
- 重用变量
$item
- 将数组传递给函数
我的问题是:是否还有其他危险情况,我们能否建立一份详尽的危险清单。或者反过来:是否可以描述什么时候不危险。
您可以通过 "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);
这个问题纯粹是学术性的,这有点hack。但是,它以一种愚蠢的编程方式很有趣。
当然它输出:
array(1) {
[0]=>string(4) "test"
}
另一方面,同样的事情在 JavaScript 中起作用,这也可以让您从参考文献中得到一些靠不住的东西。
我希望我有一个很好的例子,因为我遇到了一些 "weird" 事情,我的意思是像一些量子纠缠。这一次在 PHP 营地,我有一个递归函数(通过引用传递)和一个 foreach(通过引用传递)并且它有点在 space 时间连续体中撕开一个洞。
关于foreach
首先,对 PHP 的两个行为进行一些(可能是显而易见的)澄清:
foreach($array as $item)
将在循环后保持变量$item
不变。如果变量是引用,如foreach($array as &$item)
,即使在循环之后,它也会 "point" 到数组的最后一个元素。当一个变量是一个引用然后赋值,例如
$item = 'foo';
将更改引用指向的任何内容, 而不是 变量 ($item
) 本身。 对于后续的foreach($array2 as $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"
Note, however, that references inside arrays are potentially dangerous. Doing a normal (not by reference) assignment with a reference on the right side does not turn the left side into a reference, but references inside arrays are preserved in these normal assignments. This also applies to function calls where the array is passed by value. [...] In other words, the reference behavior of arrays is defined in an element-by-element basis; the reference behavior of individual elements is dissociated from the reference status of the array container.
以下面的例子(copy/pasted):
/* 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]
都会在全局范围内修改它。
结论
Are there other cases that are dangerous, and can we build an exhaustive list of what is dangerous. Or the other way round: is it possible to describe when it is not dangerous.
我要再次引用 PHP 文档中的话:"references inside arrays are potentially dangerous".
所以不,不可能描述什么时候不危险,因为它永远不危险。很容易忘记 $item
已创建为引用(或已创建但未销毁的全局引用),并在代码的其他地方重用它并破坏数组。这一直是争论的话题(在 this bug for example 中),人们称它为错误或功能...
接受的答案是最好的,但我想补充一下:foreach($array as &$item)
之后什么时候不需要 unset($item);
?
$item
: 以后不重复使用,就不会造成伤害。$array
:最后一个元素是引用。出于上述所有原因,这总是很危险。
那么是什么改变了元素形式成为对值的引用?
被引用次数最多:
unlink($item);
当数组从函数 return 编辑时
$item
超出范围,则数组在 return 之后变为 'normal'来自函数。function test(){ $array = [1]; foreach($array as &$item){ $item = $item; } var_dump($array); return $array; } $a = test(); var_dump($a);
array(1) { [0]=> &int(1) }
array(1) { [0]=> int(1) }但要注意: 如果你在 return 之前做任何其他事情,它可能会咬人!