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

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

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

どもです。本日の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)