Go의 포인터
포인터는 메모리 주소를 값으로 가지는 변수이다. 메모리 주솟값을 변수로 가질 수 있는 변수를 포인터 변수라고 한다.
Go는 항상 값에 의한 전달을 통해 함수를 호출한다. 포인터를 사용하면 함수 호출 시 값을 복사하는 대신 메모리 주소를 전달할 수 있다.
포인터 변수는 * 기호를 사용하여 선언한다. 변수에 & 기호를 사용하면 해당 변수의 메모리 주소를 얻을 수 있다.
1package main
2
3import "fmt"
4
5func main() {
6 var a int = 10
7 var p *int
8
9 p = &a
10
11 fmt.Println("p의 값:", p) // p의 값: 0xc0000b6010
12 fmt.Println("p가 가리키는 값:", *p) // p가 가리키는 값: 10
13 *p = 100
14 fmt.Println("a의 값:", a) // a의 값: 100
15}
16
포인터 변숫값 비교
== 연산을 통해 포인터가 같은 메모리 주소를 가리키는지 확인할 수 있다.
1package main
2
3import "fmt"
4
5func main() {
6 var a int = 10
7 var b int = 20
8 var p1 *int = &a
9 var p2 *int = &b
10
11 fmt.Println(p1 == p2) // false
12}
13
포인터 변수의 기보값은 nil이다. nil은 메모리 주소가 없음을 의미한다.
1package main
2
3import "fmt"
4
5func main() {
6 var p *int
7
8 fmt.Println(p) // false
9}
10
포인터의 사용 이유
변수 대입이나 함수 전달은 항상 값 복사를 통해 이루어진다. 이는 많은 메모리 공간을 차지하게 되므로 성능에 영향을 줄 수 있다.
인스턴스
인스턴스란 메모리에 할당된 데이터의 실체이다.
1var person Person
2var p *Person = &person
3
인스턴스를 별도로 생성하지 않고 곧바로 포인터를 생성할 수도 있다.
1var p *Person = &Person{}
2
포인터 변수가 여러개더라도 하나의 인스턴스를 가리킨다.
1var p1 *Person = &Person{}
2var p2 *Person = p1
3var p3 *Person = p1
4
하지만 대입을 통해 인스턴스를 복사하면 새로운 인스턴스가 생성된다.
1var p1 Person
2var p2 Person = p1
3var p3 Person = p1
4
위 코드에서는 총 3개의 인스턴스가 생성된다.
new 함수
new 함수를 사용하여 포인터 변수를 생성할 수 있다. new 함수는 메모리를 할당하고 해당 메모리 주소를 반환한다.
1p1 := &Person{}
2p2 := new(Person)
3
new() 함수는 인수로 타입을 받는더ㅏ. new()는 내부 필드를 원하는 값으로 초기화 할 수는 없지만, &를 사용하는 방식은 초기화를 할 수 있다.
인스턴스의 소멸
Go는 가비지 컬렉션을 지원한다. 가비지 컬렉션은 더 이상 사용되지 않는 메모리를 자동으로 해제한다. 따라서 Go에서는 메모리 누수를 방지할 수 있다.
1func Test() {
2 p := &Person{}
3 // p 사용
4 p.age = 10
5
6 // p 사용이 끝나면 메모리 해제
7}
8
일반적으로 함수가 끝나게 되면 함수 내부에서 생성된 인스턴스는 메모리에서 해제된다. 하지만 함수 내부에서 생성된 인스턴스가 함수 밖에서 사용되는 경우, 메모리가 해제되지 않는다.
1func Test() *Person {
2 p := &Person{}
3
4 return p // 탈출 분석으로 인해 p는 메모리에서 해제되지 않음
5}
6