프로그래밍Middle/Senior Go 백엔드 개발자

Go에서 슬라이스 및 map에 대한 for-range 루프는 어떻게 작동하며, 사용 시 값 복사와 관련된 특징은 무엇입니까?

Hintsage AI 어시스턴트로 면접 통과

답변.

질문 역사:

for-range 구조는 Go에서 컬렉션(슬라이스, 배열, map, 문자열) 반복을 위한 방법으로 등장했습니다. Go 개발자들은 최적화를 도입했습니다: 루프의 각 반복에서 값이 복사되고, 참조를 직접 사용하지 않기 때문에, 이는 루프 변수와 관련하여 예상치 못한 오류를 초래할 수 있습니다.

문제:

많은 사람들이 range 내 변수의 주소를 가져오려고 할 때(예: &v) 컬렉션의 요소 주소를 얻는다고 착각하지만, 실제로는 지역 변수의 주소를 얻고 있습니다.

해결책:

for-range 루프의 각 반복에서 이터레이터 변수(key, value)의 새로운 복사본이 생성됩니다. 간단한 유형의 경우에는 문제가 없지만, 구조체의 경우 요소에 대한 포인터를 유지하면 항상 같은 변수를 가리키게 되어 슬라이스의 서로 다른 요소를 가리키지 않게 됩니다.

코드 예:

people := []Person{{Name: "Ivan"}, {Name: "Oleg"}} ptrs := make([]*Person, 0) for _, p := range people { ptrs = append(ptrs, &p) // 모든 ptrs는 동일한 p를 참조하게 됩니다. }

주요 특징:

  • 각 루프 반복에서 key/value 변수의 새로운 복사본이 생성됩니다.
  • for-range 내부의 value 주소를 가져오면 컬렉션의 요소 주소가 아니라 임시 변수의 주소가 반환됩니다.
  • map의 경우, 반복은 임의의 순서로 진행되며 순서의 보장이 없습니다.

역설적인 질문.

range 내에서 value 변수에 대한 참조를 저장하면 어떻게 됩니까?

모든 참조는 같은 메모리를 가리키게 되며, value는 임시 변수입니다.

for _, v := range someSlice { ptrs = append(ptrs, &v) } // 모든 ptrs는 동일한 변수를 참조합니다!

range에서 value를 통해 컬렉션의 요소를 참조로 변경할 수 있습니까?

아니요, value를 변경해도 원래 컬렉션의 요소에는 영향을 미치지 않습니다. 변경하려면 인덱스를 통해 참조해야 합니다.

for _, v := range arr { v.Field = 10 // arr는 변경되지 않습니다. } for i := range arr { arr[i].Field = 10 // 올바른 방법입니다. }

map에 대한 for-range가 순차적인 순서를 보장합니까?

아니요, Go에서 map에 대한 반복 순서는 정의되어 있지 않으며, 각 애플리케이션 실행 시 다를 수 있습니다.

일반적인 오류 및 반패턴

  • 서로 다른 요소를 참조하기 위해 range에서 &value를 사용하는 것
  • 인덱스를 통해 참조하기보다는 value 변수를 수정하는 것
  • map의 순차적인 순서 반복을 기대하는 것

실제 사례

부정적인 사례

개발자가 range를 통해 구조체 요소의 링크 목록을 직렬화하려고 하고, &value를 별도의 슬라이스에 저장합니다. 결과적으로 동일한 주소의 슬라이스가 생성됩니다.

장점:

  • 루프 문이 간결합니다.

단점:

  • 모든 링크가 동일하므로, 하나의 요소를 변경하면 "모두"가 변경됩니다.

긍정적인 사례

인덱스를 통해 반복하고 원하는 배열 요소의 포인터를 저장합니다:

for i := range arr { ptrs = append(ptrs, &arr[i]) }

장점:

  • 각 포인터는 원래 슬라이스의 개별 요소를 가리킵니다.

단점:

  • 구문이 길지만 예측 가능한 결과입니다.