< php foreach - PHP 'foreach'는 실제로 어떻게 작동합니까?




3 Answers

예제 3에서는 배열을 수정하지 않습니다. 다른 모든 예제에서는 내용이나 내부 배열 포인터를 수정합니다. 이것은 대입 연산자의 의미 때문에 PHP 배열에서 중요합니다.

PHP의 배열에 대한 대입 연산자는 게으른 클론처럼 작동합니다. 배열을 포함하는 다른 변수에 하나의 변수를 할당하면 대부분의 언어와 달리 배열이 복제됩니다. 그러나 필요하지 않으면 실제 복제가 수행되지 않습니다. 즉, 변수 중 하나가 수정 된 경우에만 복제가 수행됩니다 (copy-on-write).

다음은 그 예입니다.

$a = array(1,2,3);
$b = $a;  // This is lazy cloning of $a. For the time
          // being $a and $b point to the same internal
          // data structure.

$a[] = 3; // Here $a changes, which triggers the actual
          // cloning. From now on, $a and $b are two
          // different data structures. The same would
          // happen if there were a change in $b.

테스트 케이스로 돌아 가면 foreach 가 배열에 대한 참조로 반복자를 생성한다고 쉽게 상상할 수 있습니다. 이 참조는 예제에서 변수 $b 와 똑같이 작동합니다. 그러나 반복자는 참조와 함께 루프 중에 만 살고 두 번 모두 무시됩니다. 이제는 모든 경우를 제외하고는 루프가 진행되는 동안 배열이 수정되고이 추가 참조는 살아 있음을 알 수 있습니다. 이것은 복제를 유발하고, 그것은 무슨 일이 일어나고 있는지를 설명합니다!

다음은이 copy-on-write 비헤이비어의 또 다른 부작용에 대한 훌륭한 기사입니다 : PHP 삼자 연산자 : 빠르거나 없습니까?

php foreach문

foreach 가 무엇이고, 사용하고, 사용하는 방법을 알고 있다고 말하면서 접두사를 붙이겠습니다. 이 질문은 보닛 아래에서 어떻게 작동하는지에 관한 것이고, "이것은 foreach 로 배열을 반복하는 방법입니다"라는 라인을 따라 답을 원하지 않습니다.

오랫동안 나는 foreach 가 배열 자체로 작업했다고 가정했다. 그런 다음 어레이의 사본 과 함께 작동한다는 사실에 대한 많은 언급을 발견했으며, 이후이 사실을 스토리의 끝으로 가정했습니다. 그러나 나는 최근에이 문제에 대한 토론을 시작했고, 약간의 실험을 거친 후에 이것이 사실 100 % 사실이 아니라는 것을 알게되었습니다.

내가 의미하는 것을 보여 드리겠습니다. 다음 테스트 케이스의 경우 다음 배열로 작업 할 것입니다.

$array = array(1, 2, 3, 4, 5);

테스트 케이스 1 :

foreach ($array as $item) {
  echo "$item\n";
  $array[] = $item;
}
print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 2 3 4 5 1 2 3 4 5 */

이것은 우리가 소스 배열과 직접 작업하지 않는다는 것을 명확하게 보여줍니다. 그렇지 않으면 루프가 계속 진행되는 동안 루프에 항목을 계속 밀어 넣기 때문에 루프가 영원히 계속됩니다. 하지만 이것이 사실인지 확인하기 위해 :

테스트 케이스 2 :

foreach ($array as $key => $item) {
  $array[$key + 1] = $item + 2;
  echo "$item\n";
}

print_r($array);

/* Output in loop:    1 2 3 4 5
   $array after loop: 1 3 4 5 6 7 */

이것은 우리의 초기 결론을 뒷받침합니다. 우리는 루프 동안 소스 배열의 사본으로 작업하고 있습니다. 그렇지 않으면 루프 중에 수정 된 값을 볼 수 있습니다. 그러나...

manual 보면 다음과 같은 내용을 알 수 있습니다.

foreach가 처음 실행을 시작하면 내부 배열 포인터가 자동으로 배열의 첫 번째 요소로 재설정됩니다.

Right ... 이것은 foreach 가 소스 배열의 배열 포인터에 의존한다고 제안하는 것 같습니다. 그러나 우리는 소스 배열을 사용하지 않는다는 것을 증명했습니다. 음, 전적으로.

테스트 케이스 3 :

// Move the array pointer on one to make sure it doesn't affect the loop
var_dump(each($array));

foreach ($array as $item) {
  echo "$item\n";
}

var_dump(each($array));

/* Output
  array(4) {
    [1]=>
    int(1)
    ["value"]=>
    int(1)
    [0]=>
    int(0)
    ["key"]=>
    int(0)
  }
  1
  2
  3
  4
  5
  bool(false)
*/

따라서 소스 배열과 직접 작업하지는 않지만 소스 배열 포인터로 직접 작업하고 있습니다. 포인터가 루프 끝에있는 배열의 끝에 있다는 사실은 이것을 보여줍니다. 이것을 제외하고는 사실 일 수 없다. 테스트 케이스 1 이 영원히 반복 될 것이다.

PHP 매뉴얼은 또한 다음과 같이 말합니다 :

foreach는 내부 배열 포인터에 의존하므로 루프 내에서 포인터를 변경하면 예기치 않은 동작이 발생할 수 있습니다.

글쎄, "예상치 못한 행동"이 무엇인지 알아 보자. (기술적으로, 나는 더 이상 기대할 것을 모르기 때문에 모든 행동이 예상치 못한 것이다.)

테스트 케이스 4 :

foreach ($array as $key => $item) {
  echo "$item\n";
  each($array);
}

/* Output: 1 2 3 4 5 */

테스트 케이스 5 :

foreach ($array as $key => $item) {
  echo "$item\n";
  reset($array);
}

/* Output: 1 2 3 4 5 */

... 예기치 않은 그 어떤 것도 실제로는 "근원의 사본"이론을지지하는 것 같습니다.

질문

여기서 무슨 일이 일어나고있는거야? C-fu는 PHP 소스 코드를 살펴봄으로써 적절한 결론을 추출 할 수있을 정도로 충분하지 않습니다. 누군가 나를 영어로 번역 할 수 있다면 감사 할 것입니다.

foreach 는 배열 복사본 을 가지고 작동하지만 소스 배열의 배열 포인터를 루프 뒤의 배열 끝에 설정합니다.

  • 이게 정확하고 전체적인 이야기입니까?
  • 그렇지 않다면 실제로 무엇을하고 있습니까?
  • foreach 중에 배열 포인터 ( each() , reset()each() 를 조정하는 함수를 사용하여 루프의 결과에 영향을 줄 수있는 상황이 있습니까?



PHP 7 참고 사항

이 답변에 대한 업데이트는 PHP 7에서 더 이상 적용되지 않습니다 : PHP 7에서 " 이전 버전과 호환되지 않는 변경 사항 "에서 설명했듯이 foreach는 배열 복사본에서 작동하므로 배열 자체의 변경 사항 foreach 루프에는 반영되지 않습니다. 링크에서 자세한 내용.

설명 ( php.net 에서 인용) :

첫 번째 형식은 array_expression에 지정된 배열을 반복합니다. 각 반복에서 현재 요소의 값이 $ value에 할당되고 내부 배열 포인터가 1만큼 앞당겨집니다 (다음 반복에서는 다음 요소를 보게됩니다).

따라서 첫 번째 예에서는 배열에 하나의 요소 만 있고 포인터가 이동하면 다음 요소가 존재하지 않으므로 새로운 요소를 추가 한 후 foreach는 이미 마지막 요소로 "결정"했기 때문에 끝납니다.

두 번째 예제에서는 두 요소로 시작하고 foreach 루프는 마지막 요소에 있지 않으므로 다음 반복에서 배열을 평가하므로 배열에 새 요소가 있음을 알게됩니다.

나는 이것이 문서의 설명의 각 반복 부분에서 모든 결과라고 믿습니다. foreach{} 의 코드를 호출하기 전에 모든 논리를 수행한다는 것을 의미합니다.

테스트 케이스

이것을 실행하면 :

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        $array['baz']=3;
        echo $v." ";
    }
    print_r($array);
?>

이 결과물을 얻을 수 있습니다 :

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

즉, 수정 사항을 수락하고 변경 사항을 적용했기 때문입니다. 그러나 이렇게하면 :

<?
    $array = Array(
        'foo' => 1,
        'bar' => 2
    );
    foreach($array as $k=>&$v) {
        if ($k=='bar') {
            $array['baz']=3;
        }
        echo $v." ";
    }
    print_r($array);
?>

당신은 얻을 것이다 :

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

즉, 배열은 수정되었지만 foreach이미 배열의 마지막 요소에 있을 때 수정 했으므로 더 이상 반복하지 않기로 결정했습니다. 새 요소를 추가했지만 "너무 늦게"추가했습니다. 끝내지 않았다.

자세한 설명 은 PHP foreach가 실제로 어떻게 작동합니까? 이 문제의 배경을 설명합니다.




PHP는 foreach 루프에서 배열을 처리하는 방식 때문에 많은 개발자, 심지어 숙련 된 개발자도 혼란스러워하기 때문에 큰 질문입니다. 표준 foreach 루프에서 PHP는 루프에서 사용되는 배열의 복사본을 만듭니다. 루프가 완료되면 즉시 복사본이 삭제됩니다. 이는 단순한 foreach 루프의 작동에서 투명합니다. 예 :

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    echo "{$item}\n";
}

이 결과는 다음과 같습니다.

apple
banana
coconut

따라서 원본 배열이 루프 내에서 참조되거나 루프가 완료된 후에 복사본이 만들어 지지만 개발자는 알지 못합니다. 그러나 루프의 항목을 수정하려고하면 완료 할 때 항목이 수정되지 않은 것으로 나타납니다.

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $item = strrev ($item);
}

print_r($set);

이 결과는 다음과 같습니다.

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
)

원본에서 변경 한 내용은 고지 사항 일 수는 없지만 실제로 $ item에 값을 지정 했더라도 원래 내용은 변경되지 않았습니다. 이것은 작업중인 $ set의 사본에 나타나는 $ item에서 작업하기 때문입니다. $ item을 참조로 잡으면 다음과 같이 덮어 쓸 수 있습니다.

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $item = strrev($item);
}
print_r($set);

이 결과는 다음과 같습니다.

Array
(
    [0] => elppa
    [1] => ananab
    [2] => tunococ
)

따라서 $ item을 참조로 조작하면 $ item의 변경 사항이 원래 $ 집합의 멤버에게 적용됩니다. 참조로 $ item을 사용하면 PHP가 배열 복사본을 만들지 못하게합니다. 이를 테스트하기 위해 먼저 사본을 보여주는 간단한 스크립트를 보여줍니다.

$set = array("apple", "banana", "coconut");
foreach ( $set AS $item ) {
    $set[] = ucfirst($item);
}
print_r($set);

이 결과는 다음과 같습니다.

Array
(
    [0] => apple
    [1] => banana
    [2] => coconut
    [3] => Apple
    [4] => Banana
    [5] => Coconut
)

위의 예제에서 볼 수 있듯이 PHP는 $ set을 복사하여 루프를 넘기 위해 사용했지만 루프 내에서 $ set을 사용하면 PHP는 변수를 복사 된 배열이 아닌 원래 배열에 추가했습니다. 기본적으로 PHP는 루프 실행과 $ item 할당을 위해 복사 된 배열을 사용하고 있습니다. 이 때문에 위의 루프는 3 번만 실행되고 원본 $ 집합의 끝 부분에 다른 값을 추가 할 때마다 원래 $ 집합을 6 개의 요소로 남겨 두지 만 무한 루프에는 입력하지 않습니다.

그러나 전에 언급 한 것처럼 $ item을 참조로 사용한 경우 어떻게됩니까? 위의 테스트에 추가 된 단일 문자 :

$set = array("apple", "banana", "coconut");
foreach ( $set AS &$item ) {
    $set[] = ucfirst($item);
}
print_r($set);

무한 루프가 발생합니다. 실제로 이것은 무한 루프이므로, 스크립트를 직접 없애거나 OS에서 메모리가 부족해질 때까지 기다려야합니다. 필자는 스크립트에 다음 줄을 추가하여 PHP에서 메모리가 매우 빨리 소모 될 수 있으므로 이러한 무한 루프 테스트를 실행하는 경우 동일한 작업을 수행하는 것이 좋습니다.

ini_set("memory_limit","1M");

무한 루프가있는 앞의 예제에서 PHP는 루프의 배열 복사본을 작성하는 이유를 알 수 있습니다. 복사본이 생성되어 루프 구조의 구조에 의해서만 사용되는 경우 배열은 루프 실행 중에 정적으로 유지되므로 문제가 발생하지 않습니다.




Related