아래 글은 http://allegro.tech/2017/07/golang-slices-gotcha.html 글을 읽고 나름 실습 및 정리해 본 내용이다.
관련 코드는 https://github.com/tomining/go_tutorial/blob/master/src/tutorial/ch3/SliceFullCapacityBugExample.go 에서 확인할 수 있음을 미리 언급한다.
들어가며
Go 언어에는 slice라는 자료 구조가 존재한다. 만약 자바 개발자라면 Vector와 비슷하다고 생각하면 쉬울 것이다.(하지만 Vector와 같진 않다.)
“Go언어 웹 프로그래밍 철저 입문” 책에서는 Array보다는 slice 타입을 더 많이 활용한다고 한다.
Array와 Slice는 유사하지만 다르다.(“Go언어 웹 프로그래밍 철저 입문” 책 P.128 표 3-13을 참고하자)
slice 특징만 몇 가지 언급해보면 아래와 같다.
- 길이가 가변적
- 참조 타입(Reference Type)
- Call by reference
- 비교 연산자(==, !=) 사용 불가
Case Study
slice의 기본 사용 문법은 책 또는 다른 자료를 통해 학습해 보기로 하고 여기서는 slice에 Append시 어떤 문제가 있을지 알아본다.
일단 샘플 코드부터 확인해 보자.
import "fmt"
func a() { x := []int{}
x = append(x, 0)
x = append(x, 1)
y := append(x, 2) z := append(x, 3) fmt.Println(y, z) }
func b() {
x := []int{}
x = append(x, 0)
x = append(x, 1)
x = append(x, 2)
y := append(x, 3) z := append(x, 4) fmt.Println(y, z) }
a()
b()
}
|
위 코드의 결과는 아마 아래와 같이 동작할 것으로 생각할 수 있다.
[0 1 2] [0 1 3]
[0 1 2 3] [0 1 2 4]
|
하지만 결과는 아래와 같다(붉은 부분 참고)
[0 1 2] [0 1 3]
[0 1 2 4] [0 1 2 4]
|
b함수에 의하면 y는 [0 1 2 3]으로 기대된다. 하지만 결과는 [0 1 2 4]이다.
왜 그럴까? 그 이유를 알려면 slice의 내부 구조와 Append의 동작원리를 알 필요가 있다.
slice 는 3가지 구성요소(pointer, length, capacity)를 갖는다.
Capacity 만큼 아이템이 저장된 뒤 추가되려고 하면 Capacity를 두 배로 늘리게 된다.(마치 자바의 Vector처럼)
그럼 Case-Study 샘플 코드를 보면서 Step-by-step으로 slice가 어떻게 변화하는지 살펴보자.
x := []int{}
x = append(x, 0)
x = append(x, 1)
|
x = append(x, 2)
|
Capacity가 두 배로 늘어난 것을 확인할 수 있다.
y := append(x, 3)
|
z := append(x, 4)
|
z 를 정의할 때 x에 append를 하게 되는데, go에서는 append시에 length 값을 기준으로 append하게 된다.
y와 z가 포인터로 같은 메모리 주소를 가르키게 되면서 y의 3이라는 값을 z의 4가 덮어 쓰게 되는 것이다.
그렇다면 a()는 예상한대로 동작할 수 있었을까?
y와 z가 생성될 때 capacity가 늘어나면서 새로운 slice를 만들어 내기 때문이다.
즉, go의 slice는 capacity가 늘어날 때만 새로운 slice를 만들어 내고 아닌 경우는 기존 slice를 그대로 활용하는 듯 하다.
참고
'Programing > Go' 카테고리의 다른 글
무작정 go 언어 시작하기(Hello Go) (0) | 2017.05.16 |
---|