../ |
---|
@tadsanさんの投稿「PHPのリファレンス(参照&)の傾向と対策、あるいはさよなら」の中に「foreachでの参照渡しは、unset()しないと危険である」との内容があったので、私も動作確認してみた。サンプルコードをより簡潔にして、やっと理解できた。
※PHP経験の長い方には、常識なのかもしれませんが、初心者には新鮮!
たとえば、
$speach = ["おはよう", "ありがと", "さよなら"];
foreach ($speach as &$s) {
echo $s, PHP_EOL;
}
foreach ($speach as $s) {
echo $s, PHP_EOL;
}
var_dump($speach);
では、2つ目のforeachの実行時に、$speach
の配列が["おはよう", "ありがと", "ありがと"]に壊れてしまうのだ。1つ目のforeachで壊れるのではなく、2つ目のforeachで壊れるのである。以下の結果になる。
おはよう
ありがと
さよなら
おはよう
ありがと
ありがと
array(3) {
[0] =>
string(12) "おはよう"
[1] =>
string(12) "ありがと"
[2] =>
string(12) "ありがと"
}
上記のコードは、以下のように展開されるそうだ。2つ目のforeachで同じ変数$s
を使うと、$s
に&$speach[2]
(2番目の要素のアドレス)が残っていて、そのアドレスに対して値をセットしにいく。実際には、C言語のような直接的なアドレスではなく、参照のための構造体を介したアドレスになる。この現象は、foreachに限らず、同じブロックのスコープ内で、同じ変数名で参照渡しを使用すると起こりえることが分かる。
$speach = ["おはよう", "ありがと", "さよなら"];
$s = &$speach[0]; // $sに&$speach[0]がセットされる
$s = &$speach[1]; // $sに&$speach[1]がセットされる
$s = &$speach[2]; // $sに&$speach[2]がセットされる
$s = $speach[0]; // &$speach[2]に0番目の要素「おはよう」がセット => ["おはよう", "ありがと", "おはよう"]
echo $s, PHP_EOL; // 0番目の要素は「おはよう」
$s = $speach[1]; // &$speach[2]に1番目の要素「ありがと」がセット => ["おはよう", "ありがと", "ありがと"]
echo $s, PHP_EOL; // 1番目の要素は「ありがと」
$s = $speach[2]; // &$speach[2]に2番目の要素「ありがと」がセット => ["おはよう", "ありがと", "ありがと"]
echo $s, PHP_EOL; // 2番目の要素は「ありがと」
var_dump($speach);
foreach ($speach as &$s)
やforeach ($speach as $s)
の$s
は、foreach のブロック内のローカル変数のように見えるが、そうではない。参照渡しにして、foreach のブロックを抜けた後で同じ変数名を使用すると、オリジナルの配列(例では$speach
)を壊してしまう。通常、最後の要素を書き換えてしまう。使用には注意が必要である。
foreachで参照渡しにしたときは、foreachの直後にunset()しないと危険だ。これは徹底しないと確かにバグを生みそう。変数名を変えれば問題ないのだが、同じ変数名で使う可能性もある。unset()を徹底した方がよさそうだ。もちろん、不要な場面で参照渡しを使わないようにするのがいいのだが。
unset()は、以下の感じで入れておこう。
$speach = ["おはよう", "ありがと", "さよなら"];
foreach ($speach as &$s) {
echo $s, PHP_EOL;
}
unset($s); // foreachで参照渡しを使った場合、同じ変数名を使うと危険であるため
foreach ($speach as $s) {
echo $s, PHP_EOL;
}
var_dump($speach);
理解のための、4つの要素でも復習しておこう。
$speach = ["おはよう", "ありがと", "さよなら", "ごめんね"];
foreach ($speach as &$s) {
echo $s, PHP_EOL;
}
foreach ($speach as $s) {
echo $s, PHP_EOL;
}
var_dump($speach);
次のように展開される。
$speach = ["おはよう", "ありがと", "さよなら", "ごめんね"];
$s = &$speach[0]; // $sに&$speach[0]がセットされる
$s = &$speach[1]; // $sに&$speach[1]がセットされる
$s = &$speach[2]; // $sに&$speach[2]がセットされる
$s = &$speach[3]; // $sに&$speach[3]がセットされる
$s = $speach[0]; // &$speach[3]に0番目の要素「おはよう」がセット => ["おはよう", "ありがと", "さよなら", "おはよう"]
echo $s, PHP_EOL; // 0番目の要素は「おはよう」
$s = $speach[1]; // &$speach[3]に1番目の要素「ありがと」がセット => ["おはよう", "ありがと", "さよなら", "ありがと"]
echo $s, PHP_EOL; // 1番目の要素は「ありがと」
$s = $speach[2]; // &$speach[3]に2番目の要素「さよなら」がセット => ["おはよう", "ありがと", "さよなら", "さよなら"]
echo $s, PHP_EOL; // 2番目の要素は「さよなら」
$s = $speach[3]; // &$speach[3]に3番目の要素「さよなら」がセット => ["おはよう", "ありがと", "さよなら", "さよなら"]
echo $s, PHP_EOL; // 3番目の要素は「さよなら」
var_dump($speach);
../ |
---|