이 글은 Tucker의 Go언어 프로그래밍 책을 14장 포인터 공부후 정리한내용이다.
포인터
포인터는 메모리 주소를 값으로 갖는 타입이다. 포인터를 이용하면 동일한 메모리 공간을 여러 변수가 가리킬수 있다.
포인터는 메모리 주소를 값으로 갖는 타입이다.
var a int
var p *int
p = &a
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) // true
fmt.Printf("p2 == p3 : %v\n", p2 == p3) // false
}
p1,p2,p3는 메모리의 주솟값을 가리킨다.
var p *int
if p! =nil{
// p가 nil이 아니라는 얘기 : 유효 메모리 주소를 가리킨다.
}
변수 대입이나 함수 인수 전달은 값을 복사하기 때문에 많은 메모리 공간을 사용하는 문제와 큰 메모리 공간 복사시 성능 문제를 갖는다. 또한 다른 공간으로 복사되기 때문에 변경사항이 적용 되지 않는다.
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) // value = 0
fmt.Printf("data[100] = %d\n", data.data[100]) // data[100] = 0
}
ChangeData() 함수 매개변수로 data 변숫값이 모두 복사되기 때문에 arg와 data는 서로 다른 메모리 공간을 갖는 변수이다. 따라서 arg값을 변경해도 data값은 변경되지 않는다. 또한 Data 구조체는 int 타입 value와 크기가 200인 int 타입 data로 구성되어 총 1608바이트가 복사 된다.
package main
import "fmt"
type Data struct {
value int
data [200]int
}
func ChnageData(arg *Data) {
arg.value = 999
arg.data[100] = 999
}
func main() {
var data Data
ChnageData(&data)
fmt.Printf("value : %d\n", data.value)
fmt.Printf("data : %d\n", data.data[100])
}
ChageData() 함수 매개변수로 Data 구조체의 포인터 즉 주소값을 받는다. arg 포인터 변수가 가리키는 구조체의 값을 변경하면 data 구조체 주솟값이기 때문에 data의 값도 변경된다. 또한 구조체 전부가 복사되는 것이 아닌 8바이트만 복사된다.
// 기존 방식
var data Data
var p *Data = &data
// 구조체를 생성해 초기화하는 방식
var p *Data = &Data{}
메모리에 할당된 데이터의 실체를 말한다.
var p1 *Data = &Data{}
var p2 *Data = p1
var p3 *Data = p1
실제로 메모리에 할당된 데이터는 한개이기때문에 인스턴스는 한개이다.
var data1 Data
var data2 Data = data1
var data3 Data = data1
메모리에 값이 세개가 할당 되었기 때문에 인스턴스는 3개가 된다.
p1 := &Data{} // &를 사용하는 초기화
var p2 = new(Data) // new()를 사용하는 초기화
대부분의 언어는 이론상 스택 메모리영역에 메모리를 할당하는것이 효율적이다. 하지만 스택 메모리는 함수 내부에서만 사용 가능한 영역이다. Go언어는 탈출 검사를 통해 메모리 공간이 함수 외부로 공개되는지 여부를 자동으로 검사해서 스택 메모리에 할당항지 힙 메모리에 할당할지 결정한다.
참고