Go로 포인터를 배열/슬라이스/맵 등등의 자료형을 통해 다루다보면 이 문제에 마주한 적이 있을 것이다.
문제가 발생하는 상황은 다음과 같다.
먼저, 재현에 필요한 구조체를 하나 만든다.
type Sample struct {
Number int
}
단순히 Number
라는 int
형 변수만을 필드로 가지고 있는 간단한 구조체이다. 예시용으로 만든 것으로 이 자리에 무엇이 오든 딱히 상관 없다.
samples := []Sample{{1}, {2}, {3}}
pointers := []*Sample{}
Sample
타입의 슬라이스를 만든 뒤 3개의 원소를 넣어주었다.
또한 pointers
라고 하는 *Sample
타입의 슬라이스를 만들었으며, 이제 여기에 값을 넣어줄 것이다.
for _, sample := range samples {
fmt.Printf("Number: %d, Address: %p\n", sample.Number, &sample)
pointers = append(pointers, &sample)
}
fmt.Println()
for _, pointer := range pointers {
fmt.Printf("Number: %d, Address: %p\n", pointer.Number, pointer)
}
값을 넣어준 뒤 제대로 들어갔는지 체크해보았다. 딱히 문제가 없어보이는데, 출력해보면
Number: 1, Address: 0xc0000ac008
Number: 2, Address: 0xc0000ac008
Number: 3, Address: 0xc0000ac008
Number: 3, Address: 0xc0000ac008
Number: 3, Address: 0xc0000ac008
Number: 3, Address: 0xc0000ac008
??? poiners
에 똑같은 값만 들어가있고 주소는 아예 다 똑같다.
사실 알고보면 간단한 문제인데... 루프가 돌면서 sample
이라고 하는 변수에 samples
의 원소를 하나씩 할당해주기 때문이다.
for _, sample := range samples {
이 루프의 첫줄이 문제였던 것이다!! 매 루프마다 새로운 sample
이 생겨나는게 아니라 루프가 시작되면서 하나의 스코프가 지정되고, 그 안에서 만들어진 sample
변수에 samples
가 할당된다.
참고로 언더스코어 해제하고 인덱스를 이용하려고 해도 동일한 문제가 발생한다.
그래서 당연히 pointers
는 같은 변수들로 가득 차게 되고, 그래서 자연히 맨 마지막 값만이 들어가는 것이다.
그렇다면 어떻게 해결하는가? Go를 쓰는 한 평생 포인터 슬라이스를 못다루는가?
루프 내에서 새 변수를 만들어서 할당해주면 된다. 심지어 같은 이름으로 선언해도 된다.
for _, sample := range samples {
sample := sample
fmt.Printf("Number: %d, Address: %p\n", sample.Number, &sample)
pointers = append(pointers, &sample)
}
fmt.Println()
for _, pointer := range pointers {
fmt.Printf("Number: %d, Address: %p\n", pointer.Number, pointer)
}
아까와 동일한 코드이며, sample := sample
한 줄만이 추가되었다.
돌려보면 놀랍게도
Number: 1, Address: 0xc00012e008
Number: 2, Address: 0xc00012e018
Number: 3, Address: 0xc00012e030
Number: 1, Address: 0xc00012e008
Number: 2, Address: 0xc00012e018
Number: 3, Address: 0xc00012e030
원하는 결과가 나온다.