[Go] 08. 포인터

2024. 1. 18. 01:25Go

반응형

1. 포인터?

포인터는 변수가 위치한 주소를 값으로 갖는 타입입니다.
데이터타입 왼쪽에 *를 붙여서 선언합니다.

*int, *float64 등...

데이터가 들어있는 변수왼쪽에 &를 붙이면 그 데이터의 주소를 얻을 수 있고,
포인터변수 왼쪽에 * 를 붙이면 포인터변수가 가리키고 있는 메모리(인스턴스)에 들어있는 실제 값을 얻을 수 있습니다.

  • 기본값은 nil
  • 함수 종료로 인해 사용을 마친 인스턴스는 가비지컬렉터에 의해 메모리 해제 됩니다.

데이터타입이 int인 변수 a의 주소를 값으로 갖고 있는 포인터 변수 p에 대한 예제를 확인해봅시다.

var a = 10  
var p *int  
p = &a  
fmt.Printf("a의 값: %d\n", a)  
fmt.Printf("p의 값: %p\n", p)  
fmt.Printf("p가 가리키고있는 메모리에 들어있는 값: %d\n", *p)  

fmt.Println("---------")  
*p = 100  
fmt.Printf("p가 가리키고있는 메모리에 들어있는 값: %d\n", *p)  
fmt.Printf("a의 값: %d", a)
a의 값: 10
p의 값: 0x14000112008
p가 가리키고있는 메모리에 들어있는 값: 10
---------
p가 가리키고있는 메모리에 들어있는 값: 100
a의 값: 100

포인터변수 p에 들어있는 값을 변경하자 a의 값도 변경된 것을 확인할 수 있습니다.

*p를 변경하는 것은 a를 변경하는 것과 같습니다.


2. 포인터를 쓰는 이유

변수 대입이나 함수인자를 전달하는 행위는 값을 복사합니다.
값을 복사하는 행위는 메모리 공간을 사용하기 때문에, 메모리 복사시 발생되는 성능적 이슈가 있습니다.

또, 복사된 값이기 때문에 복사된 값의 변경사항이 실제값에 반영되지 않는 점이 있습니다.

메모리 공간을 효율적으로 사용하고 싶거나 변경사항을 원문에도 반영하고 싶다면 포인터를 사용하는 것이 좋습니다.

go에서 함수 파라미터 값으로 구조체변수를 넘겨주는 행위는 구조체 변수값을 복사하는 행위입니다.

따라서, 아래의 예제에서, data변수는 ChangeData함수내로 복사가 되기 때문에, main함수 내의 data 와 ChangeData 함수내의 data의 주소는 각각 다르고,
ChangeData 함수내에서 값을 변경하여도, main 함수내의 data 변수는 변경되지 않습니다.

package main  

import "fmt"  

type Data struct {  
  value string  
  score [4]int  
}  

func main() {  
  var data Data  
  fmt.Printf("data.value의 주소값: %p\n", &data.value)  
  ChangeData(data)  
  fmt.Printf("data의 value: %s, score[3]: %d\n", data.value, data.score[3])  
}  

func ChangeData(data Data) {  
  fmt.Printf("ChangeData 내의 data.value의 주소값: %p\n", &data.value)  
  data.value = "temp"  
  data.score[3] = 100  
}
data.value의 주소값: 0x1400010e0c0
ChangeData 내의 data.value의 주소값: 0x1400010e0f0
data의 value: , score[3]: 0

위의 코드에서, data의 주소값을 넘겨주는 방식으로 변경합니다.

package main  

import "fmt"  

type Data struct {  
  value string  
  score [4]int  
}  

func main() {  
  var data Data  
  fmt.Printf("data.value의 주소값: %p\n", &data.value)  
  ChangeData(&data)  
  fmt.Printf("data의 value: %s, score[3]: %d\n", data.value, data.score[3])  
}  

func ChangeData(data *Data) {  
  fmt.Printf("ChangeData 내의 data.value의 주소값: %p\n", &data.value)  
  data.value = "temp"  
  data.score[3] = 100  
}
data.value의 주소값: 0x1400006a0c0
ChangeData 내의 data.value의 주소값: 0x1400006a0c0
data의 value: temp, score[3]: 100

ChangeData 함수내에서, data는 포인터변수이기 때문에 원래는 (*data).value = "temp" 와 같이 작성해야하지만, Go언어에서는 data.value = "temp" 로 설정할 수 있도록 편의를 제공하고 있습니다.

java 에서는 method에 객체의 인스턴스를 넘겨줄 때, 인스턴스명을 넘겨주는 것으로 가능했다면,
go에서는 구조체 포인터를 넘겨줘야하합니다.


한번 더 정리하자면, 아래에 있는 d1, d2, d3 인스턴스는 모두 각기 다른 인스턴스입니다. (d2, d3는 d1을 복사한 값이기 때문에)

var d1 Data
var d2 = d1
var d3 = d1

아래에 있는 a1, a2, a3 포인터 변수는 하나의 인스턴스를 가리키고 있습니다.

var a1 = &Data{}  
var a2 = a1  
var a3 = a2

3. 구조체 변수에 대한 포인터변수 초기화

구조체 변수에 & 을 붙여 포인터변수에 담을 수 있지만

type User struct {  
  Name string  
  Age  int  
}

func main() {  
  var u User  
  var p1 = &u  
  fmt.Println(*p1)
}

&User{} 와 같은 방식으로 한번에 초기값을 대입할 수도 있고, (p2의 데이터타입은 *User)

var p2 = &User{}  
fmt.Println(*p2)

new() 내장함수를 이용하여 초기화 할 수도 있습니다.

var p3 = new(User)  
fmt.Println(*p3)

4. stack, heap

보통 함수 내에서만 이용하는 값은 stack 메모리 영역을 사용하고,
함수 외부에 공개되어있는 값은 heap 메모리 영역을 사용합니다.

기본적으로, 함수 내부에서 정의한 변수의 경우, 함수를 종료한 후 가비지 컬렉터에 의해 메모리 해제되지만,
함수 내에서 생성한 변수의 주소값을 반환한다면, 이는 함수가 종료되더라도 메모리 해제가 되지 않습니다.
즉, 이 경우에는 heap 메모리 영역에 할당됩니다.

package main  

import "fmt"  

type User struct {  
  Name string  
  Age  int  
}  

func main() {  
  pUser := NewUser("jini", 32)  
  fmt.Println(*pUser)  
}  

func NewUser(name string, age int) *User {  
  return &User{Name: name, Age: age}  
}
{jini 32}
728x90
반응형

'Go' 카테고리의 다른 글

[Go] 10. 패키지  (0) 2024.01.20
[Go] 09. 문자열  (0) 2024.01.18
[Go] 07. 배열, 구조체  (1) 2024.01.17
[Go] 06. if문, switch문, for문  (0) 2024.01.16
[Go] 05. 함수, 상수  (0) 2024.01.15