[Tucker의 Go 언어 프로그래밍] 14장 포인터

Coen·2023년 10월 12일
1

tucker-go

목록 보기
11/18
post-thumbnail

이 글은 골든래빗 《Tucker의 Go 언어 프로그래밍》의 14장 써머리입니다.

14장 포인터

14.1 포인터란?

  • 메모리 주소를 값으로 갖는 타입
  • ex) int타입 변수 a가 있을 때, a는 메모리에 저장되어 있고 속성으로 메모리 주소를 가지고 있다.
  • 메모리 주소값 또한 숫자값이기 때문에 다른 변수의 값으로 사용될 수 있다.
  • 메모리 주소값을 변수값으로 가질 수 있는 변수를 포인터 변수라고 한다.

p = &a
포인터 변수 p에 a의 주소를 대입하는 구문

  • 이렇게 메모리 주소를 값으로 가져 메모리 공간을 가리키는 타입을 포인터라고 한다.
  • 포인터를 이용하면 여러 포인터 변수가 하나의 메모리 공간을 가리킬 수 있고, 포인터가 가리키고 있는 메모리 공간의 값을 읽을 수도 변경할 수도 있다.

14.1.1 포인터 변수 선언

  • 포인터 변수는 가리키는 데이터 타입 앞에 *를 붙여 선언한다.
var p *int
var f *float64
var u *User
...
  • p는 int 타입 데이터의 메모리 주소를 가리키는 포인터 변수이다.

  • 포인터 변수에 값을 채우려면 데이터 앞에 &를 붙여 메모리 주소를 받아와야 한다.

var a int
var p *int
p = &a //a의 메모리 주소를 포인터 변수 p에 대입한다.
*p = 1
t.Log(a, *p) //1 1
a = 3
t.Log(a, *p) //3 3
t.Log(&a == p) // 주소값
  • 같은 주소를 바라보고 있기 때문에 둘 다 값이 바뀐다.

14.1.2 포인터 변수값 비교하기

var a int
var p *int
p = &a //a의 메모리 주소를 포인터 변수 p에 대입한다.
t.Log(&a == p) //true

14.1.3 포인터의 기본값은 nil

  • golang은 primative variable을 제외하고는 모두 default value가 nil이다.

14.2 포인터는 왜 쓰나?

  • 변수 대입, 함수 인수 전달은 항상 값을 복사하기 때문에 메모리를 불필요하게 사용하며 리소스를 사용하게 됩니다.
  • 또한 다른 공간으로 복사되기 때문에 변경사항이 적용되지도 않는다.(golang은 함수 호출시 인수를 매개변수에 copy한다)
func valueChanger(num int) {
	num = 1
}

func valueChangerWithPointer(num *int) {
	*num = 1
}

func Test_pointerCopy(t *testing.T) {
	var a int
	valueChanger(a) //a의 값만 복사를 해가기 때문에 num 변수의 주소와 a 변수의 주소는 다름!
	t.Log(a) //0
	valueChangerWithPointer(&a) //a변수가 저장된 주소값을 가져가 그 주소에 저장된 값을 변경하기에 값이 변경된다.
	t.Log(a) //1
}

14.2.1 Data 구조체를 생성해 포인터 변수 초기화하기

  • 구조체 변수를 별도로 생성하지 않고, 곧바로 포인터 변수에 구조체를 생성해 주소를 초깃값으로 대입한다.
  • 자주 쓸만한 방법은 아닌듯 하다.
//기존방식
var data Data
var p *Data = &data
//구조체를 생성해 초기화하는 방식
var p *Data = &Data{}

14.3 인스턴스

  • 인스턴스란 메모리에 할당된 데이터의 실체를 말한다.
var data Data //Data 타입값을 저장할 메모리 공간 할당
var p *Data = &data //data의 주소만 가지고있기 때문에 메모리 공간 할당하지 않음

var p1 *Data = &Data{}
var p2 = p1
var p3 = p1 //인스턴스는 하나다.

var data1 Data
var data2 Data = data1
var data3 Data = data1 //모두 다른 인스턴스이다.

14.3.1 인스턴스 데이터의 실체다

  • 인스턴스는 메모리에 존재하는 데이터의 실체다.
  • 포인터를 이용하여 인스턴스에 접근할 수 있다.
  • 구조체 포인터를 함수 매개변수로 받는다는 말은 구조체 인스턴스로 입력을 받겠다는 이야기다.

14.3.2 new() 내장함수

p1 := &Data{} //&를 사용하는 초기화
p2 := new(Data) // new()를 사용하는 초기화
  • 타입을 메모리에 할당하고 기본값으로 채운다.
  • new를 이용하면 내부 필드값을 원하는 값으로 초기화할 수는 없다.

14.3.3 인스턴스는 언제 사라지나

  • Golang은 Garbage Collector라는 기능이 있는데, 메모리에서 쓸모없어진 데이터를 청소한다.
func TestFunc() {
	u := &User{] // u 포인터 변수를 선언하고 인스턴스 생성
    u.Age = 30
    fmt.Println(u)
} // 내부 변수 u가 사라지고 인스턴스도 사라진다.
  • 가비지컬렉터 또한 비용이 많이 들어간다.

  • 정리

    1. 인스턴스는 메모리에 생성된 데이터의 실체
    2. 포인터를 이용해 인스턴스를 가리키게 할 수 있다.
    3. 함수 호출 시 포인터 인수를 통해 인스턴스를 입력받고 그 값을 변경할 수 있게 된다.
    4. 쓸모 없어진 인스턴스는 가비지 컬렉터가 자동으로 지워준다.

14.4 스택 메모리와 힘 메모리

  • 스택 메모리는 함수 내부에서만 사용 가능한 영역.
  • 함수 외부로 공개되는 메모리 공간은 힙 메모리 영역에서 할당.
  • 자바에서는 클래스 타입을 힙에, 기본 타입을 스택에 할당
  • Golang은 탈출검사(escape analysis)를 해서 어느 메모리에 할당할지 결정.
  • 함수 외부로 공개되는 인스턴스는 함수가 종료되어도 사라지지 않는다.
  • 사라진 메모리를 가리키면 댕글링(dangling) 오류가 발생한다.
type User struct {
	Name string
    Age  int
}

func NewUser(name string, age int) *User {
	var u = User{name, age}
    return &u // 탈출 분석을 통해 메모리가 사라지지 않는다.
}

func main() {
	userPointer := NewUser("Coen", 34)
    
    fmt.Println(userPointer)
}
  • 위 코드에서 u 변수의 인스턴스가 함수 외부로 공개되는 것을 분석해내서 u를 스택 메모리가 아닌 힙 메모리에 할당한다.

핵심 요약

  1. 포인터는 메모리 주소를 값으로 갖는 타입
  2. &를 이용해 데이터의 메모리 주소를 알 수 있따.
  3. 포인터를 이용하면 메모리 주솟값으로 메모리를 조작할 수 있다.
  4. 인스턴스는 메모리에 있는 데이터 실체이고 포인터로 조작 가능
  5. Go는 탈출 분석을 통해 인스턴스를 스택 메모리에 할당할지 힙 메모리에 할당할지 결정한다.
profile
백엔드 프로그래머

0개의 댓글