Programming language/Golang

[Go] ch14 포인터

chaenii 2022. 1. 15. 21:13

본 게시물은 Tucker의 Go언어 프로그래밍을 참고해 작성한 게시물입니다.

http://www.yes24.com/Product/Goods/99108736

 

Tucker의 Go 언어 프로그래밍 - YES24

게임 회사 서버 전문가가 알려주는 Go 언어를 내 것으로 만드는 비법구글이 개발한 Go는 고성능 비동기 프로그래밍에 유용한 언어이다. 『Tucker의 Go 언어 프로그래밍』은 Go 언어로 ‘나만의 프로

www.yes24.com

 

포인터란?

포인터는 메모리 주소를 값으로 갖는 타입이다. 

메모리 주솟값을 변숫값으로 가질 수 있는 변수를 포인터 변수라고 한다.

다른 변수처럼 포인터 변수도 값을 담는 메모리 공간이 있다. 그 값으로 주소만 담을 수 있다.

int 타입 변수 n의 메모리 주소는 0xc000096040번지이고, 값으로 2020을 갖는다.

포인터 변수 nPtr은 n의 주소를 대입하는 구문이다.

포인터 변수 nPtr가 변수 n을 가리킨다.

이렇게 메모리 주소를 값으로 가져 메모리 공간을 가리키는 타입을 포인터라고 한다.

 

포인터를 이용하면 여러 포인터 변수가 하나의 메모리 공간을 가리킬 수도 있고 포인터가 가리키고 있는 메모리 공간을 읽을 수도 변경할 수도 있다.

 

포인터 변수 선언

var n int     
var nPtr *int  // 포인터 선언
nPtr = &n         // n의 메모리 주소를 포인터 변수 p에 대입한다.

nPtr은 int타입 데이터의 메모리 주소를 가리키는 포인터 변수다. float64 타입을 가리키면 *float64라고 선언하면된다.

데이터 앞에 &를 붙이면 메모리 주소를 나타낸다.

 

포인터 변수 앞에 *를 붙이면 그 포인터 변수가 가리키는 메모리 공간에 접근할 수 있다.

*nPtr = 20

nPtr가 변수 n의 메모리 공간을 가리키기 때문에 n값이 20으로 변경된다.

var n int = 5
var nPtr *int

nPtr = &n
fmt.Println("n의 값: ", n)
fmt.Println("n의 메모리 주소: ", &n)
fmt.Println("nPtr의 값: ", nPtr)
fmt.Println("nPtr이 가리키는 값: ", *nPtr)

*nPtr = 30
fmt.Println("n의 변경된 값: ", *nPtr)

----------------------------------------
n의 값:  5
n의 메모리 주소:  0xc000014090
nPtr의 값:  0xc000014090
nPtr이 가리키는 값:  5
n의 변경된 값:  30

 

포인터 변숫값 비교하기

== 연산을 사용해 포인터가 같은 메모리 공간을 가리키는지 확인할 수 있다.

var a = 1
var b = 1

var p1 *int = &a
var p2 *int = &a
var p3 *int = &b

fmt.Printf("p1 == p2 : %v\n", p1 == p2)
fmt.Printf("p1 == p3 : %v\n", p1 == p3)
-----------------------------------------
p1 == p2 : true
p1 == p3 : false

 

포인터의 기본값 nil

포인터 변숫 값을 초기화하지 않으면 기본값은 nil이다.

이 값은 0이지만 정확히 의미는 유효하지 않는 메모리 주솟값 즉 어떤 메모리 공간도 가리키고 있지 않음을 나타낸다.


포인터를 쓰는 이유

변수 대입이나 함수 인수 전달은 항상 값을 복사하기 때문에 많은 메모리 공간을 사용하는 문제와 큰 메모리 공간을 복사할 때 발생하는 성능 문제를 안고 있다. 또람 다른 공간으로 복사되기 때문에 변경 사항이 적용되지 않는다.

func changeNumber(arg int) {
	arg = 99
}

func main() {
	var a = 9

	fmt.Println("changeNumer() 이전: ", a)
	changeNumber(a)
	fmt.Println("changeNumer() 이후: ", a)
}

------------------------------------------
changeNumer() 이전:  9
changeNumer() 이후:  9

 

포인터를 이용하면 a 변수의 메모리 주소만 복사되기 때문에 메모리 주솟값인 8바이트만 복사된다.

또한, 포인터 변수 arg가 a변수의 메모리 주소를 값으로 가지고 있어서 변경된 값을 적용할 수 있다.

func changeNumber(arg *int) { // 매개변수로 int 포인터를 받는다.
	*arg = 99
}

func main() {
	var a = 9

	fmt.Println("changeNumer() 이전: ", a)
	changeNumber(&a)          // 인수로 a의 주소를 넘긴다.
	fmt.Println("changeNumer() 이후: ", a)
}

---------------------------------------------------------
changeNumer() 이전:  9
changeNumer() 이후:  99

 

 

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

구조체 변수를 별도로 생성하지 않고, 곧바로 포인터 변수에 구조체를 생성해 주소를 초깃값으로 대입할 수 있다.

var p *Data = &Data{}

인스턴스

인스턴스란 메모리에 할당된 데이터의 실체를 말한다.

예를 들어 다음 코드는 Data 타입값을 저장할 수 있는 메모리 공간을 할당한다.

var data Data

 

이렇게 할당된 메모리 공간의 실체를 인스턴스라고 부른다.

 

- Data 인스턴스 하나가 만들어지고, 포인터 변수 p가 가리킨다.

var data Data
var p *Data = &data

 

- 곧바로 인스턴스를 생성해 그 주소를 포인터 변수에 초깃값으로 대입한다.

✅ 포인터가 아무리 만아도 인스턴스가 추가로 생성되는 것은 아니다.

var p *Data = &Data{}

 

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

인스턴스 메모리에 존재하는 데이터의 실체이다.

포인터를 이용해서 인스턴스에 접근할 수 있다.

구조체 포인터를 함수 매개변수로 받는다는 말은 구조체 인스턴스로 입력을 받겠다는 의미와 같다.

 

new() 내장 함수

new() 내장 함수는 인수로 타입을 받는다. 타입을 메모리에 할당하고 기본값으로 채워 그 주소를 반환한다.

new를 사용하면 내부 필드값을 원하는 값으로 초기화할 수는 없다.

var p1 = new(Data)

 

인스턴스는 언제 사라지나

쓸모없는 데이터를 메모리에서 해제하는 기능이 필요하다.

Go언어는 가비지 컬렉터라는 메모리 청소부 기능을 제공한다.

가비지 컬렉터는 일정 간격으로 메모리에서 쓸모없어진 데이터를 청소한다.

 

'아무도 찾지 않는 데이터는 쓸모없는 데이터이다'라고 볼 수 있다.

func TestFunc() {
  u := &User{} // u 포인터 변수를 선언하고 인스턴스를 생성한다.
  u.Age = 30
  fmt.Println(u)
}              // 내부 변수 u는 사라진다. 인스턴스도 사라진다.

 

 

✅  인스턴스는 메모리에 생성된 데이터의 실체이다

✅  포인터를 이용해서 인스턴스를 가리키게 할 수 있다.

✅  함수 호출 시 포인터 인수를 통해서 인스턴스를 입력받고 그 값을 변경할 수 있다.

✅  쓸모 없어진 인스턴스는 가비지 컬렉터가 자동으로 지워준다.


스택메모리와 힙메모리

대부분 프로그래밍 언어는 메모리를 할당할 때 스택 메모리 영역 또는 힙 메모리 영역을 사용한다.

스택 메모리 영역이 힙 메모리 영역보다 훨씬 효율적이지만 스택 메모리는 함수 내부에서만 사용 가능한 영역이기 때문에 함수 외부로 공개되는 메모리 공간은 힙 메모리 영역에서 할당한다.

 

c/c++언어에서는 malloc()함수를 직접 호출해서 힙 메모리 공간을 할당한다. Go 언어는 탈출 검사를 해서 어느 메모리에 할당할지 결정한다.

 

함수 외부로 공개되는 인스턴스의 경우 함수가 종료되어도 사라지지 않는다.

type User struct {
	Name string
	Age  int
}

func NewUser(name string, age int) *User {
	var u = User{Name: name, Age: age}
	return &u // 탈출 분석으로 u 메모리가 사라지지 않음
}

func main() {
	user := NewUser("chaen", 24)

	fmt.Println(user.Name, user.Age)
}

-----------------------------------------------
chaen 24

함수 내부에서 선언된 변수는 함수가 종료되면 사라진다. 이 코드는 이미 사라진 메모리를 가리키는 댕글링 오류가 발생해야 한다.

하지만 Go언어에서는 탈출 검사를 통해서 u 변수의 인스턴스가 함수 외부로 공개되는 것을 분석해내 u를 스택 메모리가 아닌 힙 메모리에 할당한다.

메모리 공간이 함수 외부로 공개되는지 여부를 자동으로 검사해서 스택 메모리에 할당할지 힙 메모리에 할당할지 결정한다.


 

https://user-images.githubusercontent.com/48475824/73606961-79241a00-45f3-11ea-8b93-23d70838860a.png

 

반응형

'Programming language > Golang' 카테고리의 다른 글

[Go] ch16 패키지  (0) 2022.01.18
[Go] ch15 문자열  (0) 2022.01.18
[Go] ch13 구조체  (0) 2022.01.15
[Go] 12 배열  (0) 2022.01.15
[Go] 11 for문  (0) 2022.01.15