0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

PHPのforeachで参照渡しを使った後は、unset()しておこう!

Last updated at Posted at 2022-06-25
../

@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);
../
0
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?