Go 에는 메모리에 대한 것에 대해 유달리 헷갈리는 것들이 있는데, new, make
, 그리고 포인터(Pointer)이다. C언어에서 동적 메모리를 할당할 때 쓰는 malloc
, 초기화에 사용하는 memset
과는 달리 사용법이 다소 헷갈린다. 특히 Go 는 로컬 함수 스코프에서도 포인터를 반환하는 것을 허용하고 있기도 하므로 더욱 의문스럽다.
new
new()
는 메모리를 할당하되 초기화는 하지 않는다. 메모리를 할당하고 해당 객체에 제로(Zero Value)값을 설정하고 해당 객체에 대한 포인터를 반환하게 된다. Go 에서는 Zeroed Storage 라고 표현하는 듯하며 new()
를 사용하여 반환된 값은 포인터이고, 해당 포인터가 가리키는 값은 각 타입에 대한 제로값이다.
m := new(MyType)
fmt.Printf("%T %#v", m, m) // -> *main.MyType &main.MyType{}
new vs 포인터
new()
가 반환하는 것은 포인터다. 그럼, 다음의 표현과 어떤 차이가 있을까?
m := &MyType{}
fmt.Printf("%T %#v", m, m) // -> *main.MyType &main.MyType{}
사실 Effective Go 에 따르면, new()
를 사용한 것과 위의 코드는 같다고 이야기하고 있다.
new vs make
new()
는 메모리를 할당하는데, 어떤 데이터 타입에 대해서는 할당만으로는 부족하며 미리 사용할 준비가 되도록 초기화를 시켜주어야 하는 경우가 있는데, 그럴때 make()
함수를 쓴다. make()
에 사용가능 한 것은 슬라이스, 맵, 채널이며 아래의 코드에서 new()
와 make()
에 대해 차이점을 알 수 있다. 일단 make()
는 포인터를 반환하는 것이 아니라는 점이 중요하다.
먼저 슬라이스, 맵, 채널에 대해 make()
를 사용하지 않으면 nil
을 제로값으로 갖는다.
var intSlice []int
fmt.Printf("%T %#v", intSlice, intSlice) // -> []int []int(nil)
여기서 new()
를 사용해도 유사하게 나타나는데, 차이점은 포인터라는 것이다.
intSlice := new([]int)
fmt.Printf("%T %#v", intSlice, intSlice) // -> *[]int &[]int(nil)
그렇다. new()
는 단순히 제로화(Zeroed) 해주는 것 이외에는 큰 의미를 갖지 않는다. make()
를 사용하고 나서야 제대로 사용할 수 있는데, 다음과 같다. 슬라이스의 경우에는 내부 배열을 가지고 있고, 맵과 채널 같은 경우도 다소 특수한 타입으로 여겨지기 때문에 make()
를 사용하여 초기화할 필요가 있는 것이다.
//intSlice := new([]int)
//*intSlice = make([]int, 5)
intSlice := make([]int, 5)
fmt.Printf("%T %#v", intSlice, intSlice) // -> []int []int{0, 0, 0, 0, 0}
new()
와 make()
는 개인적으로 헷갈렸던 것인데 Effective Go 에서 이러한 점을 해결해주고 있어서 다행이다. new()
를 사용하는 것은 값자체에 접근할 필요는 없거나 포인터 리시버를 받는 메서드만을 호출하고 싶을때 사용하면 좋을 것 같다.