notes icon indicating copy to clipboard operation
notes copied to clipboard

PHP foreach引用赋值方式遍历完成后必须清除引用 or The reference &$value must be destroyed after the foreach loop

Open lanlin opened this issue 5 years ago • 2 comments

情景1

$x = ['zero','one','two', 'three'];

foreach ($x as &$v)
{
    // do nothing
}

foreach ($x as $v)
{
    echo $v.'-'.$x[3].PHP_EOL;
}

结果

zero-zero
one-one
two-two
two-two

说明

可以看到,由于引用赋值的变量 &$v 没有被销毁,因此 $x[3] 指向的始终是这个 &$v 的引用。

&$v 的值随着第二个 foreach 循环被不断改写时,$x[3] 的值也随着 &$v 不断变化。

直到指针循环到 [3] 三时,$x[3] 的值是上一个指针时 &$v 的值。

所以,最后 $v - $x[3] 两次打印都是 two-two

lanlin avatar Sep 26 '20 07:09 lanlin

情景2

function test(array $y): void
{
    foreach ($y as $k => $v)
    {
        $y[$k] = 'hello world!';
    }
}

$x = [1, 2, 3, 4];

foreach ($x as &$v)
{
    // do nothing
}

test($x);
print_r($x);

$v = 'abc';
print_r($x);

结果

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => hello world!
)

Array
(
    [0] => 1
    [1] => 2
    [2] => 3
    [3] => abc
)

说明

可以看到代码中 test() 方法并没有定义引用传参,而且也没有任何返回值。

但是 $x 数组还是被改变了,而且只有 $x[3] 的值被改变了。

这是因为 foreach 以引用赋值方式遍历的时候,数组最后一个元素的 &$v 引用在循环完成之后仍会保留。

所以,在循环完成后,不论是修改 $x[3] 还是 &$v 都会引起彼此的变化。

而且,由于 $x[3] 已经指向了一个引用,因此当 $x 作为参数传递时,元素 $x[3] 始终是被作为引用传递的。

所以,当在 test() 函数内改变这个引用的值时,指向这个引用的所有变量当然会跟着变。

lanlin avatar Sep 26 '20 08:09 lanlin

结论

为了避免 情景1情景2 中的情形出现,在循环完成后务必对引用变量进行销毁。

以免引起难以发现的错误。比如将引用赋值循环后的结果不断向下传递,那么一旦出现问题,Debug起来可就没有那么容易了。

解决办法

foreach ($data as &$v)
{
    // ...
}

unset($v);

lanlin avatar Sep 26 '20 08:09 lanlin