프로그래밍 언어/Go

Go: new vs make [Effective Go]

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() 를 사용하는 것은 값자체에 접근할 필요는 없거나 포인터 리시버를 받는 메서드만을 호출하고 싶을때 사용하면 좋을 것 같다.