데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      (go-07) Go Pointer 타입

      seungkyua 22.11.29
      1,730 9 1

      Go에서 Pointer 로 생성한 변수는 heap 에 저장된다. 반면에 로컬 변수나 함수가 실행될 때는 stack 에 저장된다.

      heap 에 저장되는 메모리는 랜덤하게 저장되며, 가비지 컬렉션 될 때 흝어진 메모리 값들을 클리어 시켜야 하기 때문에 성능에 영향을 줄 수 있다.

      또한 문제가 되는 것은 가비지 컬렉션은 수행시간이 있는데 이 수행시간 안에 메모리를 클리어하지 못하면 계속해서 메모리가 쌓이게 된다는 것이다.

      package main
      
      import "fmt"
      
      func main() {
        var i int64 = 10
      	var b bool = true
      	pointerI := &i
      	pointerB := &b
      	var pointerS *string
      
      	fmt.Println("pointerI =", *pointerI)
      	fmt.Println("pointerB =", *pointerB)
      	fmt.Println("pointerS =", pointerS)
      }
      
      --- output ---
      pointerI = 10
      pointerB = true
      pointerS = <nil>

      포인터는 변수의 메모리 주소를 지칭하며, & 로 메모리 주소를 표현할 수 있고 * 로 메모리 주소가 가리키는 값을 표현할 수 있다.

      다만 변수 선언 시에 사용하는 * 는 값이 아니라 포인터 변수라는 것을 표현한다.


      포인터로 선언한 변수는 포인터의 zero value 값인 nil 이 된다.

      i := 10
      pointerI := &i
      fmt.Println("memory address =", pointerI)
      fmt.Println("value =", *pointerI)
      
      var i2 *int
      fmt.Println(i2 == nil)
      fmt.Println(*i2) // panic
      
      --- optput ---
      memory address = 0xc0000ae008
      value = 10
      true
      panic: runtime error: invalid memory address or nil pointer dereference

      앞서 설명했듯이 포인터로 생성한 변수는 메모리 주소를 가리키고 있으며, 실제 메모리가 가리키는 값을 출력하려면 * 를 사용하면 된다.


      포인터 변수 값을 초기화 하지 않은 상태로 포인터 변수만 선언했을 때 값을 출력하면 panic 이 발생한다.

      여기서는 *i2 가 이런 결과를 초래했다.

      var i = new(int)      // int 의 zero value 를 pointer 로 생성함
      fmt.Println(i == nil)
      fmt.Println(*i)
      
      --- output ---
      false
      0

      new 키워드를 사용하면 해당 타입의 포인터에 zero value 인스턴스를 생성하여 할당해 준다.

      i변수는 0 값이 생성되어 포인터로 할당되었기 때문에 nil 비교가 false 이며 메모리 주소가 가리키는 값은 0 값이 출력된다.


      type Person struct {
      	Name string
      	Age  int
      }
      s := &Person{Name: "Seungkyu", Age: 50}
      fmt.Println(s)
      fmt.Println(*s)
      fmt.Println(s.Name)
      fmt.Println((*s).Name)
      
      p := &Person{}
      fmt.Println(p.Name, p.Age)
      
      --- output ---
      &{Seungkyu 50}
      {Seungkyu 50}
      Seungkyu
      Seungkyu
       0

      struct 타입은 & 를 활용하여 초기값을 지정하면서 포인터를 생성할 수 있고, new 를 사용하여 zero value 로 생성할 수 있다.


      struct 포인터는 s.Name, (*s).Name 과 같이 두가지 방법 다 필드 접근이 가능하다.

      type Person struct {
      	Name *string
      	Age  int
      }
      s := &Person{Name: &"Seungkyu"} // 에러 발생
      fmt.Println(s.Name)
      
      --- output ---
      ./main_06_pointer.go:46:21: cannot take the address of "Seungkyu"

      위와 같이 struct 의 필드 중에 string pointer 형식이 있으면 & 로 바로 값을 지정할 수 없다. 기본 타입의 경우리터럴 값에 & 로 바로 붙힐 수 없기 때문이다.

      이럴 경우 미리 변수를 선언해서 넣거나 함수를 만들어야 한다.

      아래는 포인터 변수를 선언해서 활용하는 부분이다.

      type Person struct {
      	Name *string
      	Age  int
      }
      n := "seungkyu"
      s := &Person{Name: &n}
      fmt.Println(s.Name)
      fmt.Println(*s.Name)
      
      --- output ---
      0xc000096220
      seungkyu

      혹은 아래와 같이 함수를 이용할 수 도 있다.

      func main() {
      	type Person struct {
      		Name *string
      		Age  int
      	}
      	
      	s := &Person{Name: stringToPointer("seungkyu")}
      	fmt.Println(*s.Name)
      }
      
      func stringToPointer(s string) *string {
      	return &s
      }
      
      --- output ---
      seungkyu
      func main() {
      	i := 10
      	updateAddress(&i)
      	fmt.Println(i)
      	updateValue(&i)
      	fmt.Println(i)
      }
      
      func updateAddress(i *int) {
      	a := 50
      	i = &a
      }
      
      func updateValue(i *int) {
      	*i = 50
      }
      
      --- output ---
      10
      50

      updateAddress 함수를 호출 할 때 &i 는 메모리 주소를 넘겨주는 것이고 updateAddress 함수에서 받을 때 i *int 는 메모리 주소가 복사되어 받는다(call by value).

      그러므로 updateAddress 함수 내의 i 변수 주소값을 변경하는 것은 로컬변수 i 의 주소만 변경되는 것이라 main 함수의 i 의 주소는 변경되지 않는다.


      하지만 updateValue 함수에서는 로컬변수 i 주소가 가리키는 값을 변경하는 것이기 때문에 값이 정상적으로 변경된다.

      func main() {
        p := Person{}
      	newPersonBad(&p)
      	fmt.Println(p)
      
      	p, err := newPersonGood()
      	if err != nil {
      		panic(nil)
      
      	}
      	fmt.Println(p)
      }
      
      type Person struct {
      	Name string
      	Age  int
      }
      
      func newPersonBad(p *Person) error {
      	p.Name = "seungkyu"
      	p.Age = 52
      	return nil
      }
      
      func newPersonGood() (Person, error) {
      	p := Person{
      		Name: "seungkyu",
      		Age:  52,
      	}
      	return p, nil
      }
      
      --- output ---
      {seungkyu 52}
      {seungkyu 52}

      포인터를 함수의 아규먼트로 넘겨서 값을 초기화 하는 것 보다는 함수의 리턴 값으로 초기화 하는 것이 좋은 방법이다.

      초기화를 위해 함수의 아규먼트로 포인터를 넘는 방법은 Json 을 객체로 만들 때 interface 를 넘기는 방법이 있다.

      p := struct {
      	Name string `json:"name"`
      	Age  int    `json:"age"`
      }{}
      if err := json.Unmarshal([]byte(`{"name": "seungkyu", "age": 52}`), &p); err != nil {
      	panic(err)
      }
      fmt.Println(p)
      
      --- output ---
      {seungkyu 52}

      Slice 타입을 아규먼트로 넘길 때 중요한 개념이 있다. slice 는 struct 구조 처럼 len, cap, block pinter 로 되어 있다.

      예를 들어 int 슬라이스에 1, 2, 3 값이 들어 있으면 len: 3, cap: 5, block pointer: 1, 2, 3 을 가리키는 주소 를 가진다고 할 수 있다.


      함수로 넘겨준 slice 의 len 이나 cap 값이, 함수로 받은 slice 에서 변경되면 더 이상 같은 slice 가 아니다.

      하지만 주어진 length 안에서 내부 값만 바뀐다면 그건 같은 slice 로 볼 수 있다.

      그러므로 같은 slice 로 표현하기 위해서 slice 를 생성할 때 len 을 충분히 크게 하여 생성하면 이 slice 를 마치 buffer 처럼 사용할 수 있다.

      buffer := make([]byte, 1024)
      for {
        count, err := file.Read(buffer)
        ...
      }

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      seungkyua 님의 최신 블로그

      더보기
      동영상 기고하기