web-dev-qa-db-ja.com

PHP Foreach Pass by Reference:Last Element Duplicateating?(バグ?)

私が書いていた単純なphpスクリプトを使用して、非常に奇妙な動作をしました。バグを再現するために必要な最小限に減らしました。

<?php

$arr = array("foo",
             "bar",
             "baz");

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

?>

この出力:

Array
(
    [0] => foo
    [1] => bar
    [2] => baz
)
Array
(
    [0] => foo
    [1] => bar
    [2] => bar
)

これはバグですか、それとも実際に発生するはずの奇妙な動作ですか?

155
regality

最初のforeachループの後、$itemはまだ$arr[2]によって使用されている値への参照です。そのため、参照による呼び出しではない2番目のループの各foreach呼び出しは、その値、したがって$arr[2]を新しい値に置き換えます。

ループ1の場合、値と$arr[2]$arr[0]になります。これは「foo」です。
ループ2、値と$arr[2]$arr[1]になります。これは「バー」です。
ループ3、値と$arr[2]$arr[2]になります。これは「ループ」です(ループ2のため)。

値 'baz'は、2番目のforeachループの最初の呼び出しで実際に失われます。

出力のデバッグ

ループの各反復に対して、$itemの値をエコーし​​、配列$arrを再帰的に出力します。

最初のループが実行されると、次の出力が表示されます。

foo
Array ( [0] => foo [1] => bar [2] => baz )

bar
Array ( [0] => foo [1] => bar [2] => baz )

baz
Array ( [0] => foo [1] => bar [2] => baz )

ループの最後で、$item$arr[2]と同じ場所を指し続けています。

2番目のループが実行されると、次の出力が表示されます。

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

配列が$itemに新しい値を配置するたびに、$arr[3]も同じ値で更新されることに気付くでしょう。これらは両方ともまだ同じ場所を指しているからです。ループが配列の3番目の値に到達すると、そのループの前回の反復で設定されたため、値barが含まれます。

バグですか?

いいえ。これは参照されるアイテムの動作であり、バグではありません。次のような実行に似ています。

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

Foreachループは、参照されるアイテムを無視できるという性質上、特別なものではありません。ループの外で行うように、毎回その変数を新しい値に設定するだけです。

168
animuson

$item$arr[2]への参照であり、アニメンソンが指摘したように2番目のforeachループによって上書きされています。

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

unset($item); // This will fix the issue.

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?
28
Michael Leaney

これは公式にはバグではないかもしれませんが、私の意見ではそうです。ここでの問題は、他の多くのプログラミング言語の場合のように、ループが終了したときに$itemがスコープ外になることを期待していることだと思います。しかし、そうではないようです...

このコード...

$arr = array('one', 'two', 'three');
foreach($arr as $item){
    echo "$item\n";
}    
echo $item;

出力を与える...

one
two
three
three

他の人がすでに言ったように、あなたは$arr[2]の参照変数を2番目のループで上書きしていますが、$itemがスコープ外に出なかったために起こっています。皆さんはどう思いますか...バグ?

3
jocull

これは、refディレクティブ(&)で使用するためです。最後の値は2番目のループに置き換えられ、配列が破損します。最も簡単な解決策は、2番目のループに別の名前を使用することです。

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
0
Amir Surnay

PHPの正しい動作は、私の意見ではNOTICEエラーになるはずです。foreachループで作成された参照変数がループ外で使用される場合、通知が発生するはずです。 、それが起こったときにそれを見つけるのは非常に困難です。

この種の問題を回避するには、ループの後にunset()参照する必要があります。参照に対するunset()は、元のデータを損なわずに参照を削除するだけです。

0
John

より簡単な説明は、PHPの最初の作成者であるRasmus Lerdorfによると思われます。 https://bugs.php.net/bug.php?id=71454

0
qdinar