8장 포인터

인자를 받는 함수를 호출할 때 해당 인자는 함수로 복사된다.

func zero(x int) {
    x = 0
}
func main() {
    x := 5
    zero(x)
    fmt.Println(x) // x는 여전히 5
}

이 프로그램에서 zero 함수는 main 함수에 있는 원본 x 변수를 변경하지 않을 것이다. 하지만 그렇게 하고 싶다면? 이렇게 하는 한 가지 방법은 포인터(pointer)라고 알려진 특별한 데이터 타입을 이용하는 것이다.

func zero(xPtr *int) {
    *xPtr = 0
}
func main() {
    x := 5
    zero(&x)
    fmt.Println(x) // x는 0
}

포인터는 값 자체보다는 값이 저장된 메모리상의 위치를 가리킨다(포인터는 다른 뭔가를 가리킨다). 포인터 (*int)를 이용하면 zero 함수가 원본 변수를 수정할 수 있게 된다.

8.1 *와 & 연산자

Go에서 포인터는 *(애스터리스크) 문자 다음에 저장된 값의 타입으로 나타낸다. zero 함수에서는 xPtrint에 대한 포인터에 해당한다.

*는 포인터 변수를 "역참조(dereference)"하는 데도 사용된다. 포인터를 역참조하면 해당 포인터가 가리키는 값에 접근할 수 있다. *xPtr = 0이라고 쓰면 "int 값 0을 xPtr가 참조하는 메모리 위치에 저장하라"라고 말하는 셈이다. 그렇게 하지 않고 xPtr = 0이라고 쓰면 컴파일로 오류가 발생하는데, xPtrint가 아니라 또 다른 *int만 할당할 수 있는 *int이기 때문이다.

마지막으로 변수의 주소를 구할 때는 & 연산자를 사용한다. &x*int(int에 대한 포인터)를 반환하는데, xint이기 때문이다. 이를 통해 원본 변수의 값을 변경할 수 있다. main 함수에 있는 &xzero 함수에 있는 xPtr은 동일한 메모리 위치를 참조한다.

8.2 new

포인터를 구하는 또 다른 방법은 내장 new 함수를 사용하는 것이다.

func one(xPtr *int) {
   *xPtr = 1
}
func main() {
    xPtr := new(int)
    one(xPtr)
    fmt.Println(*xPtr) // x는 1
}

new는 인자로 타입을 하나 받아 해당 타입의 값에 맞는 충분한 메모리를 할당한 후 그것에 대한 포인터를 반환한다.

일부 프로그래밍 언어에서는 new&를 사용하는 것 사이에 확연한 차이가 있으며 new로 생성한 것을 나중에 삭제할 때는 굉장히 세심한 주의를 기울여야 한다. Go에서는 상황이 조금 다른데, Go는 가비지 컬렉션(garbage collection)을 지원하는 언어로서 new로 생성한 것을 아무것도 가리키는 것이 없으면 메모리가 자동으로 정리된다.

포인터는 Go의 내장 타입에 사용되는 일이 드물지만 다음 장에서도 보겠지만 구조체와 함께 사용할 때 특히 유용하다.

연습 문제

  1. 변수의 메모리 주소를 구하는 방법은 무엇인가?

  2. 포인터에 값을 할당하는 방법은 무엇인가?

  3. 새 포인터를 생성하는 방법은 무엇인가?

  4. 다음 프로그램을 실행하고 난 후 x의 값은 무엇인가?

    func square(x *float64) {
       *x = *x * *x
    }
    func main() {
        x := 1.5
        square(&x)
    }
    
  5. 두 정수를 교환할 수 있는 프로그램을 작성하라(x := 1; y := 2; swap(&x, &y)를 실행하면 x=2이고 y=1이어야 한다).

← 이전다음 →