什么时候参考参考危险?

时间:2018-01-09 17:21:20

标签: php arrays foreach pass-by-reference

我知道,在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”   }

我很想通过我的整个代码库并在每个unset($item);之后插入foreach($array => &$item)

但是,由于这是一项重大任务并且引入了一条可能无用的行,我想知道是否有一条简单的规则可以知道foreach($array => &$item)何时是安全的,而后面没有unset($item);,并且什么时候没有。

编辑以澄清

我想我明白会发生什么以及为什么。我也知道最好做什么:foreach($array as &$item){...};unset($item);

我知道foreach($array as &$item)之后这很危险:

  • 重用变量$item
  • 将数组传递给函数

我的问题是:是否存在其他危险情况,我们是否可以建立一份详尽无遗的清单。或者反过来说:是否有可能描述它何时不危险。

3 个答案:

答案 0 :(得分:5)

关于foreach

首先,关于PHP的两种行为的一些(可能是显而易见的)澄清:

  1. foreach($array as $item)将在循环后保持变量$item不受影响。如果变量是引用,如在foreach($array as &$item)中,它将“指向”数组的最后一个元素,即使在循环之后。

  2. 当变量是参考时,则分配,例如$item = 'foo';将更改引用指向的内容,变量($item)本身。 对于后来的foreach($array2 as $item) 也是如此,如果$item 已经创建,它会将foreach视为参考,因此会修改引用所指向的内容(最后一个)在这种情况下,前一个unset中使用的数组元素。)

  3. 显然这很容易出错,这就是为什么你应该始终foreach foreach中使用的引用,以确保后续写入不会修改最后一个元素(如example #10中的function should_not_modify($array){ $array[0] = 'modified'; $array[1] = 'modified2'; } $array = ['test', 'test2']; $item = & $array[0]; should_not_modify($array); var_dump($array); 类型数组的文档。)

    关于修改数组的函数

    值得注意的是 - 正如@iainn的评论所指出 - 你的例子中的行为与array(2) { [0] => string(8) "modified" [1] => string(5) "test2" } 无关。仅存在对数组元素的引用将允许修改此元素。例如:

    /* 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

    这无疑是令人惊讶的,但explained in the PHP documentation "What References Do"

      

    但请注意,数组内部的引用可能存在危险。使用右侧的引用执行正常(非引用)赋值不会将左侧转换为引用,但是在这些正常赋值中保留数组内的引用。 这也适用于通过值传递数组的函数调用。 [...]换句话说,数组的引用行为是逐个元素定义的;单个元素的引用行为与数组容器的引用状态分离。

    使用以下示例(复制/粘贴):

    $a

    了解在创建引用时很重要,例如$b$a$b都是相同的。 $a未指向$b,反之亦然。 $item = & $array[0];$array[0]指向同一个地方。

    因此,当您执行$item时,实际上$item指向与$array[0]相同的位置。由于$item是一个全局变量,并且数组内的引用被保留,因此从任何地方(甚至从函数内部)修改{{1}}都会全局修改它。

    结论

      

    是否存在其他危险情况,我们是否可以建立一份详尽的危险清单。或者反过来说:是否有可能描述它何时不危险。

    我将再次重复PHP文档的引用:“数组内部的引用具有潜在的危险性。”

    所以不,不可能描述它何时不危险,因为它永远不会危险。很容易忘记{{1}}已被创建为引用(或者已创建并且未被销毁的全局引用),并在代码中的其他地方重用它并破坏数组。这一直是辩论的主题(this bug for example),人们称之为错误或特征......

答案 1 :(得分:0)

接受的答案是最好的,但我想补充一下:unset($item);之后foreach($array as &$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);
    
      

    array(1){     [0] =>     &安培; INT(1)   }
      array(1){     [0] =>     INT(1)   }

    但要注意:如果你在返回之前做了其他事情,它可以咬人!

答案 2 :(得分:-2)

您可以通过" 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(通过引用传递)以及它在空间时间连续体中撕裂了一个洞。