12. 포인터
12. 포인터
✅ 1. 포인터
- 메모리 주소를 값으로 가지는 타입
1 2 3 4 5 6 7 8 9
var a int = 10 var p1 *int p1 = &a // a의 메모리 주소를 포인터 변수 p에 대입 *p1 = 20 // 포인터 변수가 가리키는 a의 값을 20으로 변경 var p2 *int = &a fmt.Printf("p1 == p2 : %v\n", p1 == p2) // true
*
를 이용해서 포인터 변수를 선언하고, 포인터 변수가 가리키는 메모리 공간(실제 값)에 접근할 수 있다&
을 이용해서 메모리 공간의 주소를 가져오고 이를 포인터 변수에 대입할 수 있다- 하나의 인스턴스를 가리키는 여러 포인터 변수를 만들더라도 인스턴스가 늘어나지는 않는다
- 포인터의 기본값은
nil
이다
✅ 2. 인스턴스
- 메모리에 할당된 데이터의 실체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 1. 인스턴스 복사
var data1 Data
var data2 Data = data1
var data3 Data = data1
// 2. 포인터 복사
var p1 *Data = &Data{}
p2 := p1
p3 := p1
fmt.Printf("%p, %p, %p\n", &data1, &data2, &data3)
// 0x14000098020, 0x14000098028, 0x14000098030
fmt.Printf("%p, %p, %p\n", p1, p2, p3)
// 0x14000098038, 0x14000098038, 0x14000098038
- 1번 예시는 구조체 변수를 복사하는 것으로, 변수 값이 같은 3개의 인스턴스가 생성된다
- 2번 예시는 하나의 인스턴스를 여러 포인터가 가리키는 것으로, 인스턴스는 1개만 생성된다
2.1 인스턴스 초기화
1
2
p1 := &Data{} // 1. &를 사용하는 초기화
p2 := new(Data) // 2. new()를 사용하는 초기화
&Type{}
와 같이 포인터 변수를 초기화할 수 있다. 이 방식은p1 := &Data{3, 4}
와 같이 초기화도 가능하다new()
함수는 인수로 타입을 받는다. 타입을 메모리에 할당하고 기본값으로 초기화 후 메모리 주소를 반환한다
2.2 가비지 컬렉터
1
2
3
4
5
func TestFunc() {
u := &User{}
u.Age = 30
fmt.Println(u)
}
- 위와 같은 코드에서 u 변수는 함수가 종료되면 사라져 User 인스턴스에 접근할 수 없게 된다. 가비지 컬렉터는 일정 간격으로 메모리에서 사용하지 않는 데이터를 지워주는 역할을 한다
✅ 3. 탈출 분석 (escape analysis)
모든 인스턴스가 반드시 힙에 할당되는 게 아님!
1
2
3
4
5
6
7
8
9
10
11
package main
func f1() *int {
x := 42
return &x // 탈출 분석으로 x가 힙에 저장된다. moved to heap: x
}
func f2() {
big := [10 << 20]int{} // 메모리가 큰 경우 힙에 저장된다. moved to heap: big
_ = big
}
- Go 언어는 이스케이프 분석을 통해서 인스턴스를 스택 메모리에 할당할지 힙 메모리에 할당할지 결정한다
- 탈출 분석을 통해 함수 종료 후에도 살아있을 가능성이 있는지 판단해서 탈출 위험이 있다면 힙에 할당한다
- Go 언어에서 스택 메모리는 계속 증가되는 동적 메모리 풀이기 때문에 일정 크기를 가지는 C/C++에 비교해 메모리 효율성이 높다
This post is licensed under CC BY 4.0 by the author.