참조에 의한 PHP Foreach 패스 : 마지막 요소 복제? (곤충?)


159

방금 작성한 간단한 PHP 스크립트로 매우 이상한 행동을했습니다. 버그를 재현하는 데 필요한 최소한으로 줄였습니다.

<?php

$arr = array("foo",
             "bar",
             "baz");

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

?>

결과는 다음과 같습니다.

Array
(
    [0] => foo
    [1] => bar
    [2] => baz
)
Array
(
    [0] => foo
    [1] => bar
    [2] => bar
)

이것이 발생해야하는 버그 또는 정말 이상한 행동입니까?


값으로 다시하고, 세 번째로 변경되는지 확인하십시오 ...?
Shackrock 2011

1
@Shackrock, 값별로 반복되는 루프로 더 이상 변경되지 않는 것 같습니다.
regality 2011

1
흥미롭게도 $ item 이외의 것을 사용하도록 두 번째 루프를 변경하면 예상대로 작동합니다.
Steve Claridge 2011

9
항상 루프 본문 끝에있는 항목의 설정을 해제합니다 foreach($x AS &$y){ ... unset($y); }.-실제로는 php.net에 있습니다 (어디에 있는지 모르겠 음). 많은 실수를했기 때문입니다.
Rudie 2011

답변:


170

첫 번째 foreach 루프 $item이후에도에서 사용되는 일부 값에 대한 참조 $arr[2]입니다. 따라서 참조로 호출하지 않는 두 번째 루프의 각 foreach 호출은 해당 값을 $arr[2]새 값으로 대체합니다 .

따라서 루프 1, 값 및 $arr[2] 이 될 $arr[0]'갑'이다.
루프 2, 가치와 $arr[2]이 될 $arr[1]'바'입니다.
루프 3의 값 $arr[2]이 될 $arr[2]'바'(인해 루프 2)이다.

값 'baz'는 실제로 두 번째 foreach 루프의 첫 번째 호출에서 손실됩니다.

출력 디버깅

루프가 반복 될 때마다의 값을 에코 $item하고 배열을 재귀 적으로 인쇄합니다 $arr.

첫 번째 루프가 실행되면 다음 출력이 표시됩니다.

foo
Array ( [0] => foo [1] => bar [2] => baz )

bar
Array ( [0] => foo [1] => bar [2] => baz )

baz
Array ( [0] => foo [1] => bar [2] => baz )

루프의 끝 $item에서은 여전히와 같은 위치를 가리 킵니다 $arr[2].

두 번째 루프가 실행되면 다음 출력이 표시됩니다.

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

배열이 새로운 값을 입력 할 때마다 $item$arr[3] 동일한 위치를 가리키고 있기 때문에 동일한 값으로 업데이트 되는 방법을 알 수 있습니다. 루프가 배열의 세 번째 값에 도달하면 값이 포함됩니다.bar 의 이전 반복에 의해 방금 설정되었으므로 .

버그입니까?

아니요. 이것은 참조 된 항목의 동작이며 버그가 아닙니다. 다음과 같은 것을 실행하는 것과 유사합니다.

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

foreach 루프는 참조 된 항목을 무시할 수있는 특성상 특별하지 않습니다. 루프 외부 에서처럼 매번 해당 변수를 새 값으로 설정하는 것입니다.


4
나는 약간의 현명한 교정이 있습니다. $item에 대한 참조가 아니며에 $arr[2]포함 된 값 $arr[2]은에서 참조하는 값에 대한 참조 $item입니다. 차이를 설명하기 위해, 당신은 또한 해제 할 수 $arr[2]$item영향을받지 않을 것이고, 쓰기 것은 $item그것을 영향을 미치지 않을 것이다.
Paul Biggar 2011

2
이 동작은 이해하기 복잡하며 문제가 발생할 수 있습니다. 저는 학생들이 "참조로"일을 피해야하는 이유를 학생들에게 보여주기 위해 이것을 제가 가장 좋아하는 것 중 하나로 유지합니다.
Olivier Pons 2011

1
$itemforeach 루프가 종료 될 때 범위를 벗어나지 않는 이유는 무엇 입니까? 폐쇄 문제처럼 보입니까?
jocull 2011

6
@jocull : IN PHP, foreach, for, while 등은 자체 범위를 생성하지 않습니다.
animuson

1
@jocull, PHP에는 지역 변수가 (차단) 없습니다. 그것이 나를 괴롭히는 이유 중 하나입니다.
Qtax 2013-04-13

30

$item$arr[2]animuson이 지적한 것처럼 두 번째 foreach 루프에 대한 참조 이며 덮어 쓰입니다.

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

unset($item); // This will fix the issue.

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

3

공식적으로는 버그가 아닐 수 있지만 제 생각에는 그렇습니다. 여기서 문제는 우리가$item 는 다른 많은 프로그래밍 언어 에서처럼 루프가 종료 될 때 범위를 벗어날 . 그러나 그것은 사실이 아닌 것 같습니다 ...

이 코드는 ...

$arr = array('one', 'two', 'three');
foreach($arr as $item){
    echo "$item\n";
}    
echo $item;

출력을 제공합니다 ...

one
two
three
three

다른 사람들이 이미 말했듯이 참조 된 변수를 $arr[2]두 번째 루프로 덮어 쓰지만 $item범위를 벗어나지 않았기 때문에 발생 합니다. 어떻게 생각 하시나요? 버그?


4
1) 버그가 아닙니다. 이미 매뉴얼 에서 언급되었고 의도 한대로 많은 버그 보고서에서 무시되었습니다. 2) 질문에 실제로 답하지 않습니다 ...
BoltClock

범위 문제로 인해 나를 잡은 것이 아니라 $ item이 초기 foreach 후에 남아있을 것으로 예상했지만 foreach가 변수를 교체하는 대신 변수를 업데이트한다는 사실을 깨닫지 못했습니다. 예를 들어 두 번째 루프 전에 unset ($ item)을 실행하는 것과 동일합니다. unset은 값 (따라서 배열의 마지막 요소)을 지우지 않고 단순히 변수를 제거합니다.
Programster

불행히도 PHP는 {}일반적으로 루프 또는 블록에 대한 새로운 범위를 생성하지 않습니다 . 이것은 언어가 어떻게 작동
파비안 Schmengler


0

PHP sould의 올바른 동작은 내 의견에 NOTICE 오류입니다. foreach 루프에서 생성 된 참조 변수가 루프 외부에서 사용되면 알림이 발생합니다. 이 행동에 빠지기 매우 쉽고, 그것이 일어 났을 때 발견하기가 매우 어렵습니다. 그리고 개발자는 foreach 문서 페이지를 읽지 않을 것입니다. 그것은 도움이 아닙니다.

unset()이러한 종류의 문제를 방지하려면 루프 이후에 참조 해야 합니다. 참조에 대한 unset ()은 원본 데이터를 손상시키지 않고 참조를 제거합니다.


0

ref 지시문 (&)을 사용하기 때문입니다. 마지막 값은 두 번째 루프로 대체되고 배열이 손상됩니다. 가장 간단한 해결책은 두 번째 루프에 다른 이름을 사용하는 것입니다.

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.