GoLang을 공부하다가 문득 생각한 것이 있다.

package main

import "fmt"

type Temp struct {
	A int
	B int
}

func main () {
	tmp1 := Temp{10, 20}
	tmp2 := &Temp{10, 20}
	fmt.Println(tmp1.A, tmp1.B)
	fmt.Println(tmp2.A, tmp2.B)
}

이런 코드가 있다고 했을 때, tmp1과 tmp2는 대체 어떤 차이가 있는 것일까?

배울 때 본 것을 그대로 적용하면 tmp1에는 변수 자체가 들어간 것이고,

tmp2에는 변수의 포인터가 들어가 있기 때문에,

두 번째 Println은 자동으로 (*tmp2).A, (*tmp2).B 형식으로 참조한다고 한다.


그런데 그게 어떻게 다르다고 하는 것일까?

실제로 값을 변경해 봐도 똑같이 변경되고, 출력해도 똑같이 출력된다.


http://stackoverflow.com/questions/33593545/golang-difference-between-struct-vs-struct

인터넷을 찾아보니, 여기서 설명하기를 메서드를 이용해 구조체 멤버변수를 바꿀 때 차이가 난다고 한다.


우선 알고 가야 할 것이, GoLang에서는 구조체 내부에 포함되는 메서드라는 개념이 없다.

대신 기본 타입이 아니라면 어디에든 메서드를 갖다 붙일 수 있다.

이를 이용하여 C++이나 Java의 의 클래스 멤버 함수처럼 구현이 가능하다.


이의 구현은 아래 코드로 예시를 들 수 있다.

package main

import "fmt"

type Temp struct {
	A int
	B int
}

func (tmp *Temp) PrintVar() {
	fmt.Println(tmp.A, tmp.B)
}

func main () {
	tmp1 := Temp{10, 20}
	tmp2 := &Temp{10, 20}
	tmp1.PrintVar()
	tmp2.PrintVar()
}

결과는 둘 모두 똑같이 10과 20을 출력한다.


여기까지만 봐서는 뭐가 다른지 감이 잡히지 않을 것이다.

다른 점은 메서드 내에서 두 구조체의 변수를 변경할 때 생긴다고 한다.

흔히 말하는 Setter의 개념이다.

위 링크의 답변에 있는 소스를 그대로 가져와 보자.

package main
import "fmt"



type test_struct struct {
  Message string
}

func (t test_struct)Say (){
   fmt.Println(t.Message)
}

func (t test_struct)Update(m string){
  t.Message = m; 
}

func (t * test_struct) SayP(){
   fmt.Println(t.Message)
}

func (t* test_struct) UpdateP(m string)  {
  t.Message = m;
}

func main(){
  ts := test_struct{}
  ts.Message = "test";
  ts.Say()
  ts.Update("test2")
  ts.Say() // will still output test

  tsp := &test_struct{}
  tsp.Message = "test"
  tsp.SayP();
  tsp.UpdateP("test2")
  tsp.SayP() // will output test2

}

이를 실행하면 ts에서는 두번 다 "test"가 출력되고,

tsp에서는 두 번째 Print에서 값이 변경되어 "test2"가 출력된다.

위 링크의 답변에서는 이런 방식으로 struct{}와 &struct{} 선언의 차이가 생긴다고 한다.


의문은 여기서 생긴다.

잘 보면 ts와 tsp가 실행하는 메서드가 다르다.

ts가 실행하는 메서드는 (t test_struct) Update 이고,  tsp가 실행하는 메서드는 (t *test_struct) UpdateP 이다.

메서드의 선언부분에 차이가 있고, 애초부터 메서드명부터 다르므로 일단 다른 함수인 것이다.

혹시 저 메서드명을 같게 만들어서 struct{}와 &struct{} 각각의 선언에 따라 다른 메서드가 실행되는지 실험해보기 위해 메서드명을 같게 하고 컴파일을 시도해보기도 하였으나, 같은 메서드명이 있다며 컴파일되지 않았다.


요약하면 저 글의 답변은 struct{}와 &struct{}의 차이점이 아닌, 포인터 리시버를 갖는 메서드와 그렇지 않은 메서드의 차이를 설명하고 있는 것이다.

실제로 ts와 tsp 모두 UpdateP 메서드를 실행시키도록 하면 둘 모두 "test2"로 바뀐 결과를 확인할 수 있으며, 

둘 모두 Update 메서드를 실행시키도록 하면 둘 모두 "test"만을 출력하는 결과를 확인할 수 있다.


포인터 리시브를 하는 이유는 간단하다.

이것이 아주 좋은 예라고 생각하는데, C++에서 인자로 클래스 포인터를 넘기는 경우를 생각해 보자.

만약 클래스 포인터가 아니라 그냥 클래스 인스턴스로 인자를 넘길 경우, 해당 인스턴스가 그대로 복사되어 들어가기 때문에

함수가 실행되면서 인자가 전달될 때 오버헤드가 생기고, 내부에서 해당 클래스 멤버 변수를 변경해도 '복사된 클래스'이기 때문에

원본 클래스 멤버 변수의 값은 바뀌지 않는다.

하지만 클래스 포인터 타입으로 인자를 넘겨서 참조하여 값을 변경할 경우, 내부 멤버변수의 값을 바꾸는 것이 가능하다.


오버헤드를 줄이고, 클래스 멤버변수의 변경을 가능하게 하기 위함이라는 두 가지 이유 때문에

GoLang에서 포인터 리시브를 하여 메서드를 struct에 '갖다 붙이는' 것이다.



결론은, 구글링을 해봐도 struct{} 선언과 &struct{} 선언의 차이는 알 수가 없었다.


struct{}는 그냥 변수로 받고, &struct{}는 포인터로 받지만

&struct{}로 선언된 객체를 참조할 때는 자동으로 (*struct).var 형식으로 참조하기 때문에 동작에 다를 게 없다.

멤버변수 변조도 메서드단에서 포인터 리시버를 이용하면 struct{}든 &struct{}든 둘 다 변조가 가능하고,

포인터 리시버를 이용하지 않으면 둘 다 변조가 되지 않는다는 점에서도 똑같다.

대체 무슨 차이인 걸까...


interface를 공부하는 도중에 왜 struct{}와 &struct{}를 달리 사용하는지를 일단 알게 되었다.

우선 Minescroll 서버팀 팀원이자 후배가 직접 리버싱하여 살펴본 결과, &struct{}는 struct의 포인터를 받아오기 때문에 참조 횟수가 늘어나 struct{}에 비해 약간 더 느리다.

여기서 간과해서는 안 되는 부분이, struct{}로 받은 타입은 당연하지만 struct이고, &struct{}로 받은 타입은 *struct라는 것이다.


직접 실험해 본 결과, interface를 사용하지 않고 Pointer Receiver를 사용한 메서드를 struct에 갖다 붙였을 때,

struct{}를 사용하던 &struct{}를 사용하던 결과는 같다.

그런데 interface에서는 얘기가 조금 달라진다.

interface에서 선언만 해 둔 메서드를 이후에 (다른 OOP 언어의 말을 빌려) 오버라이딩할 경우가 있다.

이 때 메서드에 포인터 리시버를 사용하게 될 경우, 해당 함수를 실행하는 객체는 꼭 *struct 타입이어야 한다.

*struct 타입이어야 한다는 것은 struct{}로 선언한 이후, 객체 앞에 &를 붙여서 넘겨 주어도 성립한다는 것이다.

만약 멤버변수가 변하지 않아도 괜찮다면, 메서드를 정의할 때 포인터 리시버를 사용하지 않고 그냥 struct형으로 선언하여 실행하면 된다.

그럼 아래와 같은 에러는 발생하지 않을  것이다.


.\main.go:37: cannot use struc (type structType) as type MyInterface in argument to DisplayInfo:

structType does not implement MyInterface (DisplayA method has pointer receiver)

'Programming' 카테고리의 다른 글

[DLL Injection] DLL Injector  (0) 2016.10.05
[C/C++] 공용체 (union)  (0) 2016.09.22
[C++] Reference In Low-level  (0) 2016.07.02
[MITM] Create Repository 'WLAN-Crack'  (0) 2016.06.17
[MITM] TCP Packet Capture & Display  (0) 2016.06.03
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,