go - pointer

2 분 소요

go - pointer

  • 대학교 1학년 때 태어나서 처음으로 c programming을 배웠던 시절에 pointer는 뭔가 악몽 같았습니다. 실제로 많은 아이들이 떠났지만, 요즘은 오히려 pointer가 있는 것이 더 익숙하게 느껴질 때도 있어요. pointer가 있으면 이것은 reference를 참조하는 형태, 없으면 value를 참조하는 형태라는 것이 분명히 나누어지기에 가끔은 이게 더 문법 상에 정보 값을 많이 담는 것이 아닌가? 라는 생각이 들곤 합니다.
  • 아무튼, golang을 공부하다 보니 오랜만에 pointer 를 공부하게 되네요. 문법 상으로는 c와 유사한데, arrow operator인 -> 가 없는 게 조금은 낯설게 느껴집니다.

go - pointer - basic

  • pointer variable은 변수의 주소값을 기억하는 변수입니다. 혹은 변수를 가리키는 새로운 변수, 변수의 별명 이라고 생각하셔도 상관없습니다.
  • 간단하게 *int의 형태로 해당 변수가 pointer variable임을 선언할 수 있습니다.
package main

import "fmt"

func main() {
    var a int = 10
    // var b int = 20
    var p1 *int

    if p1 == nil {
        fmt.Println("pointer variable default value is nil")
    }
    p1 = &a
    fmt.Printf("a: %d \n", *p1)

    a = 20
    fmt.Printf("a: %d \n", *p1)

    *p1 = 30
    fmt.Printf("a: %d \n", *p1)

    /*
    a: 10
    a: 20
    a: 30
    */
}

go - pointer with struct

  • 당연하지만 struct에 대해서도 pointer 형식으로 메모리 주소값을 찾아서 필요한 메모리를 할당하고 값을 assign하고 변경하는 것이 가능합니다.
package main

import "fmt"

type User struct {
    id string
}

func main() {
    var user User = User{"frhyme"}
    var userP1 *User = &user

    fmt.Printf("%s \n", user.id)
    fmt.Printf("%s \n", *(&userP1.id))
    /*
    userP1은 pointer variable 인데도,
    -> 형태의 arrow operator를 사용하지 않고,
    value variable과 동일한 . operator를 사용하여 하위 field를 찾습니다.
    */
    fmt.Printf("%s \n", userP1.id)

    /*
    pointer variable을 통해 값을 변경할 수 있습니다.
    */
    userP1.id = "new_frhyme"
    fmt.Printf("%s \n", user.id)
    fmt.Printf("%s \n", *(&userP1.id))
    fmt.Printf("%s \n", userP1.id)

    /*
    pointer variable을 선언하고 memory를 할당할 수 있습니다.
    */
    var userP2 = new(User)
    userP2.id = "new_new_frhyme"
    fmt.Printf("%s \n", userP2.id)
}

golang - without arrow operator

  • golang에서는 value type이든, reference type(pointer)든 관계없이 하위 field를 지정할 때는 모두 .를 사용합니다. clang에서는 -> 이라는 arrow operator가 존재하고, 이를 사용해서 하위 field 에 접근할 수 있습니다.

clang - arrow operator

  • 아래 code에서 p1은 pointer variable이기 때문에 하위 field에 접근하기 위해서는 ->를 사용해야 합니다.
#include <stdio.h>

typedef struct User {
    int id;
} User;

int main(void) {

    User u1;
    User* p1 = &u1;

    u1.id = 10;

    /*
    u1 은 value이기 때문에 . 을 통해 field에 접근하는 반면,
    p1의 경우 reference variable이기 때문에 해당 주소 값에 할당된 memory를 찾고,
    해당 memory 내에 id 값을 찾는 방향으로 진행된다.
    */
    printf("== Output \n");
    printf("%d \n", u1.id);
    printf("%d \n", p1->id);
    printf("%d \n", (*p1).id);
    printf("%d \n", (*(&u1)).id);
    /*
    == Output
    10
    10
    10
    10
    */

    return 0;
}
  • 반면 go 에서는 -> arrow operator를 사용하지 않고 모두 . dot operator를 사용해서 처리할 수 있죠. 개발자 입장에서는 몹시 편합니다. 이유를 go.dev - Selectors에서 확인해 보면 다음과 같은데요, compiler가 알아서 pointer variable을 dereferencing 해서 처리해준다, 라고 해석하면 될 것 같네요.

As with selectors, a reference to a non-interface method with a value receiver using a pointer will automatically dereference that pointer: pt.Mv is equivalent to (*pt).Mv.

Wrap-up

  • dot operator, arrow operator를 구분하지 않고 field에 접근할 수 있기 때문에 c에 비해서 개발 생산성이 조금은 올라갈까요? 흠.

Reference

댓글남기기