2021. 5. 30. 21:39ㆍ개발/Golang
포이넡는 메모리 주소를 값으로 갖는 타입입니다. 포인터를 이용하면 동일한 메모리 공간을 여러 변수가 가리킬 수 있습니다. 그리고 메모리 복사를 줄일 수 있으며 반환값 없이 변숫값을 바꿀수 있습니다.
포인터에 대해 요약해보겠습니다!
1. 포인터란?
메모리 주소를 값으로 갖는 타입입니다.
예를 들어 int타입 a가 있을 때 a는 메모리에 저장되어 있고 속성으로 메모리 주소를 가지고 있습니다. 변수 a의 주소가 0x0100q번지라고 했을 때 메모리 주솟갑 또한 숫자값이기 때문에 다른 변수의 값으로 사용될 수 있습니다. 이렇게 메모리 주솟값을 변숫값으로 가질 수 있는 변수를 포인터 변수라고 합니다.
<사진>
p = &a
- 변수 p에 a의 주소를 대입하는 구문
- 변수 p의 값은 a의 주소인 0x0100이 됩니다. 이것을 ' 포인터 변수 p가 변수 a를 가리킨다 ' 라고 말합니다.
- 포인터를 이용하면 여러 포인터 변수가 하나의 메모리 공간을 가르킬 수도 있고 포인터가 가리키고 있는 메모리 공간의 값을 읽을 수 도 변경 할 수도 있습니다.
1) 포인터 변수 선언
var p *int
- 데이터 타입 앞에 *을 붙여서 선언합니다.
- p는 int 타입 데이터의 메모리 주소를 가리키는 포인터 변수입니다. User 구조체를 가르키면 *User라고 선언하면 됩니다.
package main
import "fmt"
func main() {
var a int = 500
var p *int // int 포인터 변수 p 선언
p = &a // a의 메모리 주소를 변수 p의 값으로 대입 (복사)
fmt.Printf("p의 값 : %p\n" , p) //p의 메모리 주소값 출력
fmt.Printf("p가 가리키는 메모리의 값: %d\n", *p) // p가 가리키는 메모리값 출력
*p = 200
fmt.Printf("a의 값 : %d\n", a)
}

- int 타입 메모리 변수 p를 선언하고 a의 메모리 주소를 p의 값에 대입합니다.
- p의 메모리 주소를 확인해보니 0xc000100010이 나왔고 거기에 넣은 값은 500이 나옵니다.
- *p로 접근한 메모리 변수 p에 200으로 변경하니 a의 주소를 가리킨 p의 값때문에 값이 200으로 변경됩니다.
2) 포인터 변숫값 비교하기
package main
import "fmt"
func main() {
var a int = 10
var b int = 20
var p1 *int = &a
var p2 *int = &a
var p3 *int = &b
fmt.Printf("p1 == p2 : %v\n", p1==p2)
fmt.Printf("p2 == p3 : %v\n", p2==p3)
}

- p1은 a의 메모리공간을 p2도 a의 메모리공간을 가지고 p3은 b의 메모리공간을 가집니다.
- p1과 p2는 같은 메모리 주소를 가지기 때문에 p1 == p2는 true가 되고, p2 == p3는 다르기 때문에 false가 됩니다.
3) 포인터의 기본값 nil
포인터 변숫값을 초기화 하지않으면 기본값은 nil입니다. 이값은 0이지만 정확한 의미는 유효하지 않는 메모리 주솟값 즉 어떤 메모리 공간도 가리키고 있지 않음을 나타냅니다.
< 유효한 메모리 주소를 가르키는지 검사하는 구문>
var p *int
if p != nil {
// p가 nil이 아니라는 얘기는 p가 유효한 메모리 주소를 가리킨다는 뜻입니다.
}
2. 포인터는 왜 쓰나?
변수 대입이나 함수 인수 전달은 항상 값을 복사하기 때문에 많은 메모리 공간을 사용하는 문제와 큰 메모리 공간을 복사할 때 발생하는 성능 문제를 안고 있습니다. 또한 다른 공간으로 복사되기 때문에 변경사항이 적용되지도 않습니다. 포인터를 사용하지 않는 예를 살펴보겠습니다.
< 예시 >
package main
import "fmt"
type Data struct {
value int
data [200]int
}
func ChangeData(arg Data) {
arg.value = 999
arg.data[100] = 999
}
func main() {
var data Data
ChangeData(data)
fmt.Printf("value = %d\n", data.value)
fmt.Printf("data[100] = %d\n",data.data[100])
}

- ChangeData() 함수는 Data 타입 구조체를 매개변수로 받습니다. ChangeData()에서 Data함수를 호출하면서 인수로 넣습니다.
- ChangeData() 함수의 매개변수 arg와 data는 서로 다른 메모리 공간을 갖는 변수입니다.
- arg 변수값을 변경해도 data 변수와는 다른 메모리 공간을 가지기 때문에 arg값을 변경해도 data값은 출력되지 않습니다.
- 이 예제의 문제점
- ChangeData() 함수 호출시 data 변숫값이 모두 복사되기 때문에 구조체 크기 만큼 복사된다.
- Data 구조체의 크기는 1608바이트입니다. ( int = 8byte , [200]int = 1600byte )
- 함수를 호출 할때마다 1608바이트를 짧은 시간에 많이 호출되면 성능 문제가 발생할 수 있습니다.
→ 이 문제들을 한방에 해결해주는 해결사가 포인터 입니다.
< 포인터를 이용한 예제 >
package main
import "fmt"
type Data struct {
value int
data [200]int
}
func ChangeData(arg *Data) { //매개변수로 Data 포인터를 받습니다.
arg.value = 999
arg.data[100] = 999
}
func main() {
var data Data // Data 구조체의 변수 data
ChangeData(&data)
fmt.Printf("value = %d\n", data.value)
fmt.Printf("data[100] = %d\n", data.data[100])
}

- ChangeData() 함수 매개변수로 Data 구조체의 포인터를 받는것으로 변경했습니다.
- data는 변숫값이 아니라 data의 메모리 주소를 인수로 전달합니다. 메모리주소는 8바이트 숫자값 ( 64비트 컴퓨터에선 메모리주소는 8바이트 ) 이기에 1608바이트가 복사되는게 아닌 8바이트만 복사 됩니다.
- arg 포인터 변수가 가리큰 구조체 값을 변경합니다. ( 999의 값 )
- arg 포인터 값은 main() 함수의 data 구조체 주솟값이기 때문에 arg 포인터가 main() 함수의 data 변수를 가리키게 됩니다. 그래서 data값이 변경됩니다.
- Data 구조체를 생성해 포인터 변수 초기화하기
구조체 변수를 별도로 생성하지 않고, 곧바로 포인터 변수에 구조체를 생성해 주소를 초깃값으로 대입하는 방법

- Data 타입 포인터 변수 p에 Data구조체를 생성해 그 주소를 대입했습니다.
- 실제로 있는 구조체 데이터의 실체를 가리키게 되므로 포인터 변수 p만 가지고도 구조체의 필드 값에 접근하고 변경할 수 있습니다.
2. 인스턴스



1) new() 내장 함수

2) 인스턴스는 언제 사라지나?
인스턴스는 아무도 찾지 않을 때 사라진다.
func TestFunc() {
u := &User{} // u 포인터 변수를 선언하고 인스턴스를 생성합니다.
u.Age = 30
fmt.Println(u)
} // 내부 변수 u는 사라집니다. 더불어 인스턴스도 사라집니다.
< 정리 >
- 인스턴스는 메모리에 생성된 데이터의 실체입니다.
- 포인터를 이용해서 인스턴스를 가르키게 할 수있습니다.
- 함수 호출 시 포인터 인수를 통해서 인스턴스를 입력받고 그 값을 변경할 수 있게 됩니다.
- 쓸모 없어진 인스턴스는 가비지 컬렉터가 자동으로 지워줍니다.
3) 스택메모리와 힙메모리
- 프로그래밍 언어는 메모리를 할당할 때 스택메모리 영역 또는 힙 메모리 영역을 사용
- 스택메모리는 효율적이지만 스택메모리는 함수 내부에서만 사용가능한 영역
- 함수 외부로 공개되는 메모리 공간은 힙 메모리영역에서 할당합니다.
- Go언어 에서는 탈출 검사를 통해서 변수의 인스턴스가 함수 외부로 공개되는것을 분석해 스텍메모리가 아닌 힙메모리에서 할당하게 됩니다.
ps. 스택메모리와 힙메모리와 인스턴스 탈출 검사는 Tucker님 유튜브나 책을 보시는 걸 추천. 글로 설명이 어렵네요.ㅎ
'개발 > Golang' 카테고리의 다른 글
| Golang Concurrency - Channel (0) | 2022.08.08 |
|---|---|
| Golang Concurrency - Gorutine (1) (0) | 2022.08.05 |
| 『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트 : 13강 구조체 (0) | 2021.05.28 |
| 『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트 : 12강 배열 (0) | 2021.05.27 |
| 『Tucker의 Go 언어 프로그래밍』 스터디 요약 노트 : 11강 for문 (0) | 2021.05.27 |