ど素人から毛を生やす。<延>

配列の末尾ポインタが謎にズレた原因がforeachの参照渡しだった話。

Web > PHP 2022年5月25日(最終更新:2年前)

2022年5月25日に作成されたページです。
情報が古かったり、僕が今以上のど素人だった頃の記事だったりする可能性があります。

どもです。本日のPHPの失敗談。
やりたかったのは、端的に示すとこういうもの。

$parent = array(
	0 => 'a',
	1 => 'b',
	2 => 'c',
);

foreach($parent as &$child){
	preg_replace("/[^-+0-9]/", '', $child);
}

$new_array = array();
foreach($parent as $id => $child){
	$new_array[$id] = $child;
}

print_r($new_array);

この実行結果について、あるべき結果は、こう。

Array(
    [0] => a
    [1] => b
    [2] => c
)

実行した結果が、これ。

Array(
    [0] => a
    [1] => b
    [2] => b
)

...... why?????

色々と試してみた結果、以下の記述ならそれぞれあるべき姿になることが判明。

foreach($parent as $id => $child){
	$parent[$id] = preg_replace("/[^-+0-9]/", '', $child);
}
$new_array = array();
foreach($parent as $id => $new_child){
	$new_array[$id] = $new_child;
}
foreach($parent as &$child){
	preg_replace("/[^-+0-9]/", '', $child);
}

unset($child);

$new_array = array();
foreach($parent as $id => $child){
	$new_array[$id] = $child;
}

どうやら、foreachで参照渡しを行った後、その変数に参照の状態が残り続けていて、そのまま再利用しようとするとこのようにバグるらしい。

具体的な原理は、2回目のforeachのときの$childを$parent[2]に置き換えると分かり易い。

$new_array = array();
foreach($parent as $id => $parent[2]){
	$new_array[$id] = $parent[2];
}

//	↓↓

$id = 0;
$parent[2] = $parent[0];	   //$parent[0] = 'a' を代入するので、$parent[2] = 'a'
$new_array[0] = $parent[2];

$id = 1;
$parent[2] = $parent[1];	   //$parent[1] = 'b' を代入するので、$parent[2] = 'a'→'b'
$new_array[1] = $parent[2];

$id = 0;
$parent[2] = $parent[2];	   //$parent[2] = 'b' を代入するので、$parent[2] = 'b'→'b'
$new_array[2] = $parent[2];

なーーーーるほど。言われてみれば、とても納得。
だから配列の末尾だけひとつ前の値になって、あたかもポインタがズレたように見えたと。

改めて調べてみたら、foreach参照渡しバグ?はまあまあ有名なものらしいですね。
なんてこった。


参考:
[Qiita] PHP foreach 参照渡し 罠

この記事は役に立ちましたか?
  • _(:3」∠)_ 面白かった (0)
  • (・∀・) 参考になった (0)
  • (`・ω・´) 役に立った (0)