Post

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.