프로그래밍 언어/PHP

PHP 'foreach'의 작동 원리

Rateye 2021. 8. 5. 10:35
728x90
반응형
질문 : 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 */

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

매뉴얼 을 보면 다음과 같은 내용이 있습니다.

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

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() 등)를 조정하는 함수를 사용하면 루프의 결과에 영향을 미칠 수있는 상황이 있습니까?
답변

foreach 는 세 가지 다른 종류의 값에 대한 반복을 지원합니다.

다음에서는 반복이 다른 경우에 어떻게 작동하는지 정확하게 설명하려고합니다. 지금까지 가장 간단한 경우는 Traversable 객체입니다. foreach 는 본질적으로 다음 줄을 따라 코드에 대한 구문 설탕 일뿐입니다.

foreach ($it as $k => $v) { /* ... */ }

/* translates to: */

if ($it instanceof IteratorAggregate) {
    $it = $it->getIterator();
}
for ($it->rewind(); $it->valid(); $it->next()) {
    $v = $it->current();
    $k = $it->key();
    /* ... */
}

내부 클래스의 경우 기본적으로 C 수준 Iterator 인터페이스를 미러링하는 내부 API를 사용하여 실제 메서드 호출을 방지합니다.

배열과 일반 객체의 반복은 훨씬 더 복잡합니다. 우선, PHP에서 "배열"은 실제로 정렬 된 사전이며이 순서에 따라 순회됩니다 ( sort 와 같은 것을 사용하지 않는 한 삽입 순서와 일치 함). 이것은 키의 자연스러운 순서 (다른 언어의 목록이 자주 작동하는 방식)를 반복하거나 정의 된 순서가 전혀없는 (다른 언어의 사전이 자주 작동하는 방식)에 반대됩니다.

객체 속성은 속성 이름을 해당 값에 매핑하는 다른 (순서화 된) 사전과 일부 가시성 처리로 볼 수 있으므로 객체에도 동일하게 적용됩니다. 대부분의 경우 개체 속성은 실제로 이처럼 비효율적 인 방식으로 저장되지 않습니다. 그러나 객체에 대해 반복을 시작하면 일반적으로 사용되는 패킹 된 표현이 실제 사전으로 변환됩니다. 그 시점에서 일반 객체의 반복은 배열의 반복과 매우 유사하게됩니다 (이것이 여기에서 일반 객체 반복에 대해 많이 논의하지 않는 이유입니다).

여태까지는 그런대로 잘됐다. 사전을 반복하는 것은 너무 어렵지 않습니다. 문제는 반복 중에 배열 / 객체가 변경 될 수 있음을 인식 할 때 시작됩니다. 이 문제가 발생할 수있는 여러 가지 방법이 있습니다.

  • foreach ($arr as &$v) 를 사용하여 참조로 반복하면 $arr 가 참조로 바뀌고 반복 중에 변경할 수 있습니다.
  • PHP 5에서는 값으로 반복하더라도 동일하게 적용되지만 배열은 미리 참조였습니다. $ref =& $arr; foreach ($ref as $v)
  • 객체에는 by-handle 전달 의미 체계가 있으며, 이는 대부분의 실제 목적에서 참조처럼 작동 함을 의미합니다. 따라서 반복 중에 객체를 항상 변경할 수 있습니다.

반복 중에 수정을 허용하는 문제는 현재있는 요소가 제거 된 경우입니다. 포인터를 사용하여 현재 어떤 배열 요소에 있는지 추적한다고 가정 해 보겠습니다. 이 요소가 이제 해제되면 매달린 포인터가 남게됩니다 (보통 segfault가 발생 함).

이 문제를 해결하는 방법에는 여러 가지가 있습니다. 이 점에서 PHP 5와 PHP 7은 크게 다르며 다음에서 두 가지 동작을 모두 설명하겠습니다. 요약하면 PHP 5의 접근 방식은 다소 멍청하고 모든 종류의 이상한 엣지 케이스 문제를 야기하는 반면, PHP 7의 더 복잡한 접근 방식은 더 예측 가능하고 일관된 동작을 제공합니다.

마지막 예비로서, PHP는 메모리를 관리하기 위해 참조 카운팅과 쓰기시 복사를 사용한다는 점에 유의해야합니다. 즉, 값을 "복사"하면 실제로 이전 값을 재사용하고 참조 횟수 (refcount)를 증가시킵니다. 어떤 종류의 수정을 수행 한 후에 만 실제 복사본 ( "중복"이라고 함)이 수행됩니다. 이 주제에 대한보다 광범위한 소개 는 당신이 거짓말을하고 있습니다를 참조하십시오.

PHP 5

내부 어레이 포인터 및 HashPointer

PHP 5의 배열에는 수정을 적절히 지원하는 전용 "내부 배열 포인터"(IAP)가 하나 있습니다. 요소가 제거 될 때마다 IAP가이 요소를 가리키는 지 확인합니다. 그렇다면 다음 요소로 넘어갑니다.

foreach 는 IAP를 사용하지만 추가적인 복잡성이 있습니다. IAP는 하나 뿐이지 만 하나의 어레이는 여러 foreach 루프의 일부가 될 수 있습니다.

// Using by-ref iteration here to make sure that it's really
// the same array in both loops and not a copy
foreach ($arr as &$v1) {
    foreach ($arr as &$v) {
        // ...
    }
}

내부 배열 포인터가 하나 뿐인 두 개의 동시 루프를 지원하기 위해 foreach 는 다음과 같은 헛소리를 수행합니다. 루프 본문이 실행되기 전에 foreach 는 현재 요소에 대한 포인터와 해당 해시를 foreach HashPointer 합니다. 루프 본문이 실행 된 후 IAP가 여전히 존재하는 경우이 요소로 다시 설정됩니다. 그러나 요소가 제거 된 경우 IAP가 현재있는 모든 위치에서 사용합니다. 이 계획은 대부분 일종의 작업이지만, 여기서 얻을 수있는 이상한 동작이 많이 있습니다. 그 중 일부는 아래에서 설명하겠습니다.

배열 복제

IAP는 COW (Copy-On-Write) 의미 체계에 따른 수정으로 인해 IAP 카운트가 변경되는 것과 같은 어레이의 가시적 기능입니다 ( current foreach 가 반복되는 배열을 복제해야하는 경우가 많다는 것을 의미합니다. 정확한 조건은 다음과 같습니다.

  1. 배열이 참조가 아닙니다(is_ref=0). 참조인 경우 변경 내용이 전파되어야 하므로 중복되지 않아야 합니다.
  2. 어레이에 refcount>1이 있습니다. refcount가 1이면 어레이가 공유되지 않으므로 직접 수정할 수 있습니다.

어레이가 중복되지 않은 경우 (is_ref = 0, 참조 카운트 = 1) 만의 refcount 증가 될 것이다 (*). 또한 foreach by reference가 사용되면 (잠재적으로 중복 된) 배열이 참조로 바뀝니다.

이 코드를 중복이 발생하는 예로 고려하십시오.

function iterate($arr) {
    foreach ($arr as $v) {}
}

$outerArr = [0, 1, 2, 3, 4];
iterate($outerArr);

여기서, $arr IAP가에 변화 방지하기 위해 중복됩니다 $arr 하기 누출 $outerArr . 위의 조건에서 배열은 참조가 아니며 (is_ref = 0) 두 위치에서 사용됩니다 (refcount = 2). 이 요구 사항은 유감스럽고 차선책 구현의 결과물입니다 (여기서 반복하는 동안 수정에 대한 우려가 없으므로 처음에 실제로 IAP를 사용할 필요가 없습니다).

(*) refcount 것은 무해한 것처럼 들리지만 COW (copy-on-write) 의미론을 위반합니다. 이것은 refcount = 2 배열의 IAP를 수정한다는 것을 의미하는 반면 COW는 수정이 refcount에서만 수행 될 수 있음을 지시합니다. = 1 값. 이 위반으로 인해 사용자가 볼 수있는 동작 변경이 발생합니다 (COW는 일반적으로 투명하지만). 반복 된 어레이의 IAP 변경은 관찰 가능하지만 어레이의 첫 번째 비 IAP 수정 전까지 만 가능합니다. 대신 세 가지 "유효한"옵션은 a) 항상 복제하는 것, b) refcount 증가시키지 않고 따라서 반복 된 배열이 루프에서 임의로 수정되도록 허용하거나 c) IAP를 전혀 사용하지 않는 것입니다. PHP 7 솔루션).

 

반응형

 

Position advancement order

아래 코드 샘플을 올바르게 이해하기 위해 알아야 할 마지막 구현 세부 사항이 있습니다. 일부 데이터 구조를 반복하는 "정상적인"방법은 의사 코드에서 다음과 같습니다.

reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
    code();
    move_forward(arr);
}

그러나 foreach 는 다소 특별한 눈송이이기 때문에 약간 다르게 작업을 선택합니다.

reset(arr);
while (get_current_data(arr, &data) == SUCCESS) {
    move_forward(arr);
    code();
}

즉, 배열 포인터는 루프 본문이 실행 되기 전에 이미 앞으로 이동되었습니다. 즉, 루프 본문이 $i 요소에서 작업하는 동안 IAP는 이미 $i+1 요소에 있습니다. 이것이 반복 중 수정을 보여주는 코드 샘플이 항상 현재 요소가 아닌 다음 unset

예: 테스트 사례

foreach 구현의 특이성에 대한 대부분의 완전한 인상을 제공해야하며 몇 가지 예를 논의 할 수 있습니다.

테스트 케이스의 동작은이 시점에서 간단하게 설명 할 수 있습니다.

  • 테스트 케이스 1과 2에서 $array 는 refcount = 1로 시작하므로 foreach refcount 만 증가합니다. 루프 본문이 이후에 배열 (해당 지점에서 refcount = 2가 있음)을 수정하면 해당 지점에서 복제가 발생합니다. $array 의 수정되지 않은 복사본에 대해 계속 작업합니다.
  • 테스트 케이스 3에서 다시 한 번 배열이 복제되지 않으므로 foreach $array 변수의 IAP를 수정합니다. 반복이 끝날 때 IAP는 NULL (반복이 완료 each false 를 반환하여 나타냅니다.
  • 테스트 케이스 4와 5에서 eachreset 은 모두 참조 기능입니다. $array refcount=2 를 가지므로 복제해야합니다. 따라서 foreach 는 별도의 어레이에서 다시 작업합니다.

 

Examples: Effects of current in foreach

다양한 복제 동작을 보여주는 좋은 방법은 foreach current() 함수의 동작을 관찰하는 것입니다. 이 예를 고려하십시오.

foreach ($array as $val) {
    var_dump(current($array));
}
/* Output: 2 2 2 2 2 */

current() 는 배열을 수정하지 않더라도 by-ref 함수 (실제로는 prefer-ref)라는 것을 알아야합니다. next 와 같은 다른 모든 기능을 잘 사용하기 위해서는 모두 참조가 필요합니다. 참조에 의한 전달은 배열을 분리해야 함을 의미하므로 $arrayforeach-array 가 달라집니다. 1 대신 2 를 얻는 이유도 위에서 언급했습니다. foreach 는 사용자 코드 를 실행하기 전에 배열 포인터를 진행합니다. 따라서 코드가 첫 번째 요소에 있더라도 foreach 이미 포인터를 두 번째 요소로 이동했습니다.

이제 약간의 수정을 시도해 보겠습니다.

$ref = &$array;
foreach ($array as $val) {
    var_dump(current($array));
}
/* Output: 2 3 4 5 false */

여기에 is_ref = 1 케이스가 있으므로 배열이 복사되지 않습니다 (위와 같이). current() 함수에 전달할 때 더 이상 배열을 복제 할 필요가 없습니다. 따라서 current()foreach 는 동일한 배열에서 작동합니다. foreach 가 포인터를 전진시키는 방식으로 인해 여전히 off-by-one 동작을 볼 수 있습니다.

by-ref 반복을 수행 할 때 동일한 동작을 얻습니다.

foreach ($array as &$val) {
    var_dump(current($array));
}
/* Output: 2 3 4 5 false */

여기서 중요한 부분은 foreach가 $array 를 is_ref = 1로 만들 것이므로 기본적으로 위와 동일한 상황을 갖게됩니다.

또 다른 작은 변형입니다. 이번에는 배열을 다른 변수에 할당합니다.

$foo = $array;
foreach ($array as $val) {
    var_dump(current($array));
}
/* Output: 1 1 1 1 1 */

$array 의 refcount는 루프가 시작될 때 2입니다. 그래서 일단 우리는 실제로 미리 복제를해야합니다. 따라서 $array 와 foreach에서 사용하는 배열은 처음과 완전히 분리됩니다. 이것이 루프 이전에 있던 IAP의 위치를 얻는 이유입니다 (이 경우 첫 번째 위치에 있음).

Examples: Modification during iteration

반복하는 동안 수정 사항을 고려하는 것은 모든 foreach 문제가 발생한 곳이므로이 경우에 대한 몇 가지 예를 고려하는 데 도움이됩니다.

동일한 배열에 대한 다음 중첩 루프를 고려하십시오 (실제로 동일한 배열인지 확인하기 위해 by-ref 반복이 사용됨).

foreach ($array as &$v1) {
    foreach ($array as &$v2) {
        if ($v1 == 1 && $v2 == 1) {
            unset($array[1]);
        }
        echo "($v1, $v2)\n";
    }
}

// Output: (1, 1) (1, 3) (1, 4) (1, 5)

여기서 예상되는 부분은 1 이 제거 (1, 2) 가 출력에서 누락된다는 것입니다. 예상치 못한 것은 첫 번째 요소 다음에 외부 루프가 중지된다는 것입니다. 왜 그런 겁니까?

그 이유는 위에서 설명한 중첩 루프 해킹 때문입니다. 루프 본문이 실행되기 전에 현재 IAP 위치와 해시가 HashPointer 백업됩니다. 루프 본문 이후에 복원되지만 요소가 여전히 존재하는 경우에만, 그렇지 않으면 현재 IAP 위치 (어떠한 것이 든)가 대신 사용됩니다. 위의 예에서 이것은 정확히 해당되는 경우입니다. 외부 루프의 현재 요소가 제거되었으므로 내부 루프에 의해 이미 완료된 것으로 표시된 IAP를 사용합니다!

HashPointer 백업 + 복원 메커니즘의 또 다른 결과는 reset() foreach 영향을 미치지 않는다는 것입니다. 예를 들어 다음 코드는 reset() 이 전혀없는 것처럼 실행됩니다.

$array = [1, 2, 3, 4, 5];
foreach ($array as &$value) {
    var_dump($value);
    reset($array);
}
// output: 1, 2, 3, 4, 5

그 이유는 reset() 이 IAP를 일시적으로 수정하는 동안 루프 본문 이후의 현재 foreach 요소로 복원되기 때문입니다. reset() 이 루프에 영향을 미치도록 강제하려면 현재 요소를 추가로 제거해야 백업 / 복원 메커니즘이 실패합니다.

$array = [1, 2, 3, 4, 5];
$ref =& $array;
foreach ($array as $value) {
    var_dump($value);
    unset($array[1]);
    reset($array);
}
// output: 1, 1, 3, 4, 5

그러나 이러한 예는 여전히 정상입니다. HashPointer 복원이 요소에 대한 포인터와 해당 해시를 사용하여 여전히 존재하는지 여부를 확인한다는 사실을 기억하면 진짜 재미가 시작됩니다. 그러나 : 해시는 충돌이 있으며 포인터를 재사용 할 수 있습니다! 즉, 배열 키를 신중하게 선택하면 제거 된 요소가 여전히 존재한다고 foreach 예 :

$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
$ref =& $array;
foreach ($array as $value) {
    unset($array['EzFY']);
    $array['FYFY'] = 4;
    reset($array);
    var_dump($value);
}
// output: 1, 4

여기서 우리는 일반적으로 이전 규칙에 따라 1, 1, 3, 4 어떻게되는지 'FYFY' 는 제거 된 요소 'EzFY' 와 동일한 해시를 가지며 할당자는 요소를 저장하기 위해 동일한 메모리 위치를 재사용합니다. 따라서 foreach는 새로 삽입 된 요소로 직접 점프하여 루프를 단축시킵니다.

마지막으로 언급하고 싶은 이상한 경우는 PHP를 사용하면 루프 중에 반복 된 엔티티를 대체 할 수 있다는 것입니다. 따라서 한 어레이에서 반복을 시작한 다음 중간에 다른 어레이로 교체 할 수 있습니다. 또는 배열에서 반복을 시작한 다음 객체로 바꿉니다.

$arr = [1, 2, 3, 4, 5];
$obj = (object) [6, 7, 8, 9, 10];

$ref =& $arr;
foreach ($ref as $val) {
    echo "$val\n";
    if ($val == 3) {
        $ref = $obj;
    }
}
/* Output: 1 2 3 6 7 8 9 10 */

이 경우에서 볼 수 있듯이 PHP는 대체가 발생하면 처음부터 다른 엔티티를 반복하기 시작합니다.

PHP 7

Hashtable iterators

여전히 기억한다면 배열 반복의 주요 문제는 반복 중간에 요소 제거를 처리하는 방법이었습니다. PHP 5는이 목적을 위해 단일 내부 배열 포인터 (IAP)를 사용했는데, 이는 하나의 배열 포인터가 여러 개의 동시 foreach 루프 그 위에 reset() 등과의 상호 작용을 지원하기 위해 확장되어야했기 때문에 다소 차선책이었습니다.

PHP 7은 다른 접근 방식을 사용합니다. 즉, 임의의 양의 안전한 외부 해시 테이블 반복기 생성을 지원합니다. 이러한 반복기는 배열에 등록되어야하며, 그 지점에서 IAP와 동일한 의미를 갖습니다. 배열 요소가 제거되면 해당 요소를 가리키는 모든 해시 테이블 반복기가 다음 요소로 진행됩니다.

이것은 foreach 가 더 이상 IAP 를 전혀 사용하지 않음을 의미합니다. foreach current() 등의 결과에 전혀 영향을 미치지 않으며 자체 동작은 reset() 등과 같은 함수의 영향을받지 않습니다.

Array duplication

PHP 5와 PHP 7의 또 다른 중요한 변경 사항은 배열 복제와 관련이 있습니다. 이제 IAP가 더 이상 사용되지 않으므로 refcount 배열 반복은 모든 경우에 배열을 복제하는 대신 refcount 증분 만 수행합니다. foreach 루프 중에 어레이가 수정되면 해당 시점에서 복제가 발생하고 (기록 중 복사에 따라) foreach 는 이전 어레이에서 계속 작업합니다.

대부분의 경우이 변경 사항은 투명하며 더 나은 성능 외에 다른 효과는 없습니다. 그러나 다른 동작이 발생하는 경우가 있습니다. 즉 배열이 미리 참조 된 경우입니다.

$array = [1, 2, 3, 4, 5];
$ref = &$array;
foreach ($array as $val) {
    var_dump($val);
    $array[2] = 0;
}
/* Old output: 1, 2, 0, 4, 5 */
/* New output: 1, 2, 3, 4, 5 */

이전에는 참조 배열의 값별 반복이 특별한 경우였습니다. 이 경우 중복이 발생하지 않았으므로 반복 중 배열의 모든 수정 사항이 루프에 반영됩니다. PHP 7에서는이 특별한 경우가 사라졌습니다. 배열의 값에 의한 반복은 루프 중 수정 사항을 무시하고 항상 원래 요소에서 계속 작동합니다.

물론 이것은 참조에 의한 반복에는 적용되지 않습니다. 참조로 반복하면 모든 수정 사항이 루프에 반영됩니다. 흥미롭게도 일반 객체의 값별 반복에 대해서도 마찬가지입니다.

$obj = new stdClass;
$obj->foo = 1;
$obj->bar = 2;
foreach ($obj as $val) {
    var_dump($val);
    $obj->bar = 42;
}
/* Old and new output: 1, 42 */

이것은 객체의 핸들 별 의미를 반영합니다 (즉, 값별 컨텍스트에서도 참조처럼 동작 함).

Examples

테스트 사례부터 시작하여 몇 가지 예를 살펴 보겠습니다.

  • 테스트 케이스 1과 2는 동일한 출력을 유지합니다. 값별 배열 반복은 항상 원래 요소에서 계속 작동합니다. (이 경우에도 refcounting 및 복제 문제 정확히 PHP 5와 7 사이 PHP 동일하다).
  • 테스트 케이스 3 변경 : Foreach 는 더 이상 IAP를 사용하지 않으므로 each() 는 루프의 영향을받지 않습니다. 이전과 이후에 동일한 출력을 갖습니다.
  • 테스트 케이스 4와 5는 동일하게 유지됩니다. each()reset() 은 IAP를 변경하기 전에 어레이를 복제하지만 foreach 여전히 원래 어레이를 사용합니다. (어레이가 공유 된 경우에도 IAP 변경은 중요하지 않습니다.)

 

두 번째 예제 세트는 다른 reference/refcounting current() 의 동작과 관련이 있습니다. current() 가 루프의 영향을 완전히받지 않으므로 반환 값이 항상 동일하게 유지되므로 더 이상 의미가 없습니다.

그러나 반복 중 수정을 고려할 때 몇 가지 흥미로운 변경 사항이 있습니다. 나는 당신이 새로운 행동을 더 잘 찾을 수 있기를 바랍니다. 첫 번째 예 :

$array = [1, 2, 3, 4, 5];
foreach ($array as &$v1) {
    foreach ($array as &$v2) {
        if ($v1 == 1 && $v2 == 1) {
            unset($array[1]);
        }
        echo "($v1, $v2)\n";
    }
}

// Old output: (1, 1) (1, 3) (1, 4) (1, 5)
// New output: (1, 1) (1, 3) (1, 4) (1, 5)
//             (3, 1) (3, 3) (3, 4) (3, 5)
//             (4, 1) (4, 3) (4, 4) (4, 5)
//             (5, 1) (5, 3) (5, 4) (5, 5)

보시다시피 외부 루프는 첫 번째 반복 후에 더 이상 중단되지 않습니다. 그 이유는 두 루프 모두 이제 완전히 분리 된 해시 테이블 반복기를 가지며 더 이상 공유 IAP를 통한 두 루프의 교차 오염이 없기 때문입니다.

지금 해결 된 또 다른 이상한 경우는 동일한 해시를 갖는 요소를 제거하고 추가 할 때 얻을 수있는 이상한 효과입니다.

$array = ['EzEz' => 1, 'EzFY' => 2, 'FYEz' => 3];
foreach ($array as &$value) {
    unset($array['EzFY']);
    $array['FYFY'] = 4;
    var_dump($value);
}
// Old output: 1, 4
// New output: 1, 3,

이전에 HashPointer 복원 메커니즘은 (해시와 포인터가 충돌하여) 제거 된 요소와 동일한 것처럼 "보였기 때문에"새 요소로 바로 이동했습니다. 더 이상 요소 해시에 의존하지 않으므로 더 이상 문제가되지 않습니다.

출처 : https://stackoverflow.com/questions/10057671/how-does-php-foreach-actually-work
728x90
반응형