프로그래밍 개념 정리

Go 메서드에서 포인터 변환

Dr.Mlem 2025. 3. 31. 07:53
func multiplyApi(ctx *fasthttp.RequestCtx) {
	log.Printf("[INPUT] %s\n", ctx.PostBody())

	req := request{}
	if err := json.Unmarshal(ctx.PostBody(), &req); err != nil {
		ctx.SetStatusCode(400)
		ctx.SetBodyString("Invalid Json format!!")
		return
	}

	ctx.SetBodyString(strconv.Itoa(req.A * req.B))
}

 

Q.
이 경우, ctx는 포인터 변수인 것으로 추정하는데, ctx.PostBody() 이렇게, 포인터 변수 안에 PostBody()값으로 접근이 가능한거야?

 

 

 

A.

좋은 질문이야! Go의 포인터와 메서드 호출 방식에 대한 감각이 생기고 있다는 뜻이야 👏
그럼 하나씩 정확하게 짚어볼게.


✅ 결론 먼저!

ctx는 포인터 타입이 맞고,
ctx.PostBody()처럼 포인터 변수에서 바로 메서드를 호출하는 것은 완전히 가능해!


🔍 왜 가능한가?

Go에서는 메서드가 포인터 리시버로 정의되어 있을 경우,
포인터 변수는 물론이고 일반 변수에서도 자동으로 메서드 호출이 가능하게 해줘.

예를 들어:

type Person struct {
    name string
}

func (p *Person) SayHi() {
    fmt.Println("Hi, my name is", p.name)
}

func main() {
    p := &Person{name: "Alice"}
    p.SayHi() // ✅ p는 *Person → 메서드 호출 OK
}

여기서 SayHi()는 *Person 타입에 정의된 메서드인데,
p가 포인터 타입이니까 그냥 p.SayHi() 해도 아무 문제 없이 작동하지.


🔍 실제 질문 예제와 연결해 보면

func multiplyApi(ctx *fasthttp.RequestCtx) {
    ...
    ctx.PostBody()  // ✅ 정상
}
  • ctx는 *fasthttp.RequestCtx 타입
  • PostBody()는 func (ctx *RequestCtx) PostBody() []byte
    👉 즉, 포인터 리시버 메서드
  • ctx는 포인터니까 → ctx.PostBody()는 당연히 호출 가능 ✅

📌 추가 팁

Go는 아주 편리하게도:


메서드 리시버 타입 호출 대상 가능 여부
func (p *T) 포인터 *T ✅ 가능
func (p *T) 값 T ✅ 가능 (자동으로 주소 취함)
func (p T) 값 T ✅ 가능
func (p T) 포인터 *T ✅ 가능 (자동으로 역참조함)

🧠 정리

  • 네 말이 맞아! ctx는 포인터 타입이고, ctx.PostBody()도 잘 작동해.
  • Go는 메서드 호출 시 포인터/값 타입을 자동으로 맞춰주기 때문에 굳이 (*ctx).PostBody()처럼 쓸 필요가 없어.
  • 대신, 포인터냐 값이냐에 따라 메서드 내부에서 변수 변경이 반영되는지 여부는 달라지니 주의!

필요하면 fasthttp 내부 구현 구조나 RequestCtx 관련 메서드도 살펴봐줄게!
추가 궁금한 거 있으면 언제든 물어봐 😄

 
 
 

Q2.

그러니까 원래대로 ctx가 가리키는 메모리 주소의 값을 바꾸려면, (*ctx).PostBody()가 맞는거야? 그런데 이거 왜 괄호랑 *이게 사라질 수 있는건지 다시 한 번 설명해주겠어?

 

 

 

A2.

ChatGPT의 말:

좋아, 정말 중요한 부분을 집요하게 파고드는 거 아주 잘하고 있어 👏
지금 질문은 Go의 포인터 메서드 호출 문법과 그 내부 동작 원리에 대한 거야.
바로 본론부터 차근차근 정리해줄게!


✅ 질문 핵심 요약

원래대로라면 (*ctx).PostBody()가 맞는 거 아닌가?
그런데 왜 ctx.PostBody()처럼 괄호랑 * 없이도 호출이 되는 건가?


🧠 결론부터 말하자면

Go는 메서드 호출 시, 포인터인지 값인지에 따라 자동으로 * 또는 &를 추가해줌
그래서 (*ctx).PostBody()와 ctx.PostBody()는 동일하게 동작함


🔍 직접 비교해서 보기

예제 구조체:

type Sample struct {
    value int
}

func (s *Sample) Print() {
    fmt.Println("value is", s.value)
}

이제 아래 코드를 봐봐:

s := &Sample{value: 42}
s.Print()         // ✅ 정상 작동
(*s).Print()      // ✅ 이것도 정상 작동 (원래 모양)

여기서 s는 포인터니까 진짜로 메서드를 호출하려면 (*s).Print()가 맞아.
그런데 Go는 네가 귀찮지 않게 하기 위해 이렇게 말하는 거야:

“어? 이 메서드는 *Sample용이네. 너가 그냥 s.Print()라고 썼지만 * 붙여줄게!”

즉, Go 컴파일러가 자동으로 (*s)로 변환해서 호출해줌
그 결과 두 표현은 완전히 동일하게 동작하는 거야.


🔧 왜 이렇게 자동으로 되게 만들었을까?

  • Go는 C처럼 너무 딱딱한 문법보단, 실용성과 생산성을 더 중요시
  • 그래서 개발자가 "이건 포인터인데 꼭 괄호 치고 * 붙여야 하나?" 하는 불편을 줄이려고
  • **"자동 포인터 변환"**을 메서드 호출에 한정해서 제공함

🧪 네 코드에 적용

func multiplyApi(ctx *fasthttp.RequestCtx) {
    ctx.PostBody()        // ✅ Go가 자동으로 (*ctx).PostBody()로 처리해줌
}

PostBody()는 func (ctx *RequestCtx)로 정의된 메서드인데,
ctx는 *RequestCtx 타입이니까
→ 그냥 ctx.PostBody()라고 써도 자동으로 (*ctx).PostBody()가 호출됨


⚠️ 단, 모든 곳에서 그런 건 아님!

이 자동 포인터 변환은 "메서드 호출할 때만" 적용돼.

s := &Sample{value: 10}
fmt.Println(s.value)    // ✅ 필드 접근도 자동으로 dereference 됨

var s2 *Sample
s2.value = 5            // ✅ 이것도 OK

(*s2).value = 5         // ✅ 이것도 OK

var s3 Sample
s3Ptr := &s3
s3Ptr.Print()           // ✅ 자동으로 (*s3Ptr).Print()

✅ 요약 정리

표현 의미 동작 여부
(*ctx).PostBody() 원래 정석 형태 (직접 역참조) ✅ 가능
ctx.PostBody() Go가 자동으로 역참조해줌 ✅ 가능
ctx.value 포인터의 필드 접근도 자동 역참조 ✅ 가능
*ctx = something 직접 역참조 연산 ✅ 필요 (메서드 아님)

이제 Go가 왜 메서드 호출에서만 자동으로 *를 생략할 수 있게 해줬는지,
그리고 언제는 생략 가능하고 언제는 아닌지 완벽하게 이해한 거야 🔥

더 궁금한 포인터 관련 케이스 있으면 얼마든지 도와줄게!

 
 

 

 

 

 

'프로그래밍 개념 정리' 카테고리의 다른 글

self 인스턴스와 변수의 스코프  (0) 2025.04.01
이중 포인터, 포인터 배열  (0) 2025.03.31
Go 포인터 공부  (0) 2025.03.29
Go에서의 에러 확인용 문법  (0) 2025.03.27
포인터로 참조  (0) 2025.03.26