PHP の foreach でポインタにまつわる変な挙動に遭遇しました。
PHP のバージョンは 5.2.4 です。
foreach で配列のポインタを使う
例えば以下のようなコード。
<?php
$array = array("a", "b", "c");
foreach ($array as $index => $item) {
if ($index == 2) {
// 3番目の要素だけ「X」に変える
$item = 'X';
}
}
var_dump($array);
?>
これの出力は以下のようになります。
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "c"
}
3番目の要素の値が「X」になってませんね。
これはコードを以下のように書き換えることで期待通りに動作させることができます。
<?php
$array = array("a", "b", "c");
foreach ($array as $index => &$item) {
if ($index == 2) {
// 3番目の要素だけ「X」に変える
$item = ‘X’;
}
}
var_dump($array);
?>
foreach の中で使用する変数である「$item」の頭に「&」を付けています。
こうすることで、変数「$item」が配列の各要素へのポインタになります。
これを実行すると以下のように出力されました。
array(3) {
[0]=>
string(1) "a"
[1]=>
string(1) "b"
[2]=>
string(1) "X"
}
期待通りですね。
よかったよかった。
って、これで終わりじゃないんです。
配列の挙動が。。。
次に、以下のようなプログラムを実行してみます。
<?php
$array = array("a", "b", "c");
foreach ($array as $index => &$item) {
if ($index == 2) {
// 3番目の要素だけ「X」に変える
$item = ‘X’;
}
}
//var_dump($array);
foreach ($array as $index => $item) {
echo $item . “\n”;
}
?>
さっきのプログラムの後ろに foeach で配列の要素を順番に出力する処理を付け足しているだけです。
これを実行すると以下が出力されました。
a b b
ん?、最後の要素は「X」のはずなのに「b」と出力されています。
どういうことでしょう?
いろいろ実験した結果、以下のコードでこのおかしな現象を再現できることがわかりました。
$lt;?php
$array = array("a", "b", "c");
foreach ($array as $index =$gt; &$item) {
}
var_dump($array);
foreach ($array as $index =$gt; $item) {
}
var_dump($array);
?$gt;
二つの foreach ループを回しているだけです。
一回目の foreach ではループ変数に要素へのポインタを指定しています。
これを実行すると以下のように出力されます。
array(3) {
[0]=$gt;
string(1) "a"
[1]=$gt;
string(1) "b"
[2]=$gt;
&string(1) "c"
}
array(3) {
[0]=$gt;
string(1) "a"
[1]=$gt;
string(1) "b"
[2]=$gt;
&string(1) "b"
}
二回目の foeach の後に配列の中身がおかしなことになってますね。
配列の最後の要素が「c」ではなく「b」になってます。
なんでこんなことになるんでしょうか?
ていうか、何もしない foreach 文を二つ書くだけで配列の挙動がおかしくなってしまうなんて素敵な言語ですね!
調べた
ちょっと調べてみたんですが、
PHP 5 以降、$value の前に & を付けることで、 容易に配列の要素の値を変更できるようになっています。 これにより、値をコピーするのではなく、 リファレンス が代入されます。
[略]
警告
foreach ループを終えた後でも、 $value は配列の最後の要素を参照したままとなります。 unset() でその参照を解除しておくようにしましょう。
ということらしいです。
foreach でループ変数にポインタを使った場合、foreach を抜けた後に unset() を呼ぶ必要があるんですねぇ。
(foreach 側で自動でやっておいてくれたらいいのに。。)
というわけで、さっきのコードを以下のように変えてみました。
$lt;?php
$array = array("a", "b", "c");
foreach ($array as $index =$gt; &$item) {
}
var_dump($array);
// ポインタをリセット
unset($item);
foreach ($array as $index =$gt; $item) {
}
var_dump($array);
?$gt;
一回目の foreach の後に「unset($item);」を追加しています。
出力は以下のようになりました。
array(3) {
[0]=$gt;
string(1) "a"
[1]=$gt;
string(1) "b"
[2]=$gt;
&string(1) "c"
}
array(3) {
[0]=$gt;
string(1) "a"
[1]=$gt;
string(1) "b"
[2]=$gt;
&string(1) "c"
}
おお、ちゃんと期待通りの値が出力されました!
でもまだ納得いかないんですよねぇ。
ポインタをリセットしないと二回目の foreach で配列がおかしくなるっていうのが解せないです。
なんでだろ。
PHP ってほんと難しい言語ですね。
こういうところが PHP の魅力なのかもしれませんね(違っ)。
追記
こちらの解説が詳しいです。
foreachの$valueを参照で受けると思わぬバグを引き起こす - ぱせらんメモ
そういうことだったんですね。




