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



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 삼자 연산자 : 빠르거나 없습니까?

Question

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.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);
?>

You will get:

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

Which means that array was modified, but since we modified it when the foreach already was at the last element of the array, it "decided" not to loop anymore, and even though we added new element, we added it "too late" and it was not looped through.

Detailed explanation can be read at How does PHP 'foreach' actually work? which explains the internals behind this behaviour.




Great question, because many developers, even experienced ones, are confused by the way PHP handles arrays in foreach loops. In the standard foreach loop, PHP makes a copy of the array that is used in the loop. The copy is discarded immediately after the loop finishes. This is transparent in the operation of a simple foreach loop. 예 :

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

This outputs:

apple
banana
coconut

So the copy is created but the developer doesn't notice, because the original array isn't referenced within the loop or after the loop finishes. However, when you attempt to modify the items in a loop, you find that they are unmodified when you finish:

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

print_r($set);

This outputs:

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

Any changes from the original can't be notices, actually there are no changes from the original, even though you clearly assigned a value to $item. This is because you are operating on $item as it appears in the copy of $set being worked on. You can override this by grabbing $item by reference, like so:

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

This outputs:

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

So it is evident and observable, when $item is operated on by-reference, the changes made to $item are made to the members of the original $set. Using $item by reference also prevents PHP from creating the array copy. To test this, first we'll show a quick script demonstrating the copy:

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

This outputs:

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

As it is shown in the example, PHP copied $set and used it to loop over, but when $set was used inside the loop, PHP added the variables to the original array, not the copied array. Basically, PHP is only using the copied array for the execution of the loop and the assignment of $item. Because of this, the loop above only executes 3 times, and each time it appends another value to the end of the original $set, leaving the original $set with 6 elements, but never entering an infinite loop.

However, what if we had used $item by reference, as I mentioned before? A single character added to the above test:

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

Results in an infinite loop. Note this actually is an infinite loop, you'll have to either kill the script yourself or wait for your OS to run out of memory. I added the following line to my script so PHP would run out of memory very quickly, I suggest you do the same if you're going to be running these infinite loop tests:

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

So in this previous example with the infinite loop, we see the reason why PHP was written to create a copy of the array to loop over. When a copy is created and used only by the structure of the loop construct itself, the array stays static throughout the execution of the loop, so you'll never run into issues.




Related