공대생 정리노트

defer - 실수하기 쉬운 인자 및 body의 값 계산 시점 본문

언어/Go

defer - 실수하기 쉬운 인자 및 body의 값 계산 시점

woojinger 2022. 3. 19. 16:27

https://go.dev/doc/effective_go#defer

 

Effective Go - The Go Programming Language

Effective Go Introduction Go is a new language. Although it borrows ideas from existing languages, it has unusual properties that make effective Go programs different in character from programs written in its relatives. A straightforward translation of a C

go.dev

Effective Go를 보면 defer가 실행하는 함수의 인자에 들어가는 값은 defer가 실행이 될 때 정해진다(defer가 함수를 call할 때가 아니라)

또한 defer는 마치 스택처럼 LIFO 순서대로 실행이 된다.

 

이 부분을 기억하고, 다음 예제를 보자.

package main

import "fmt"

type A struct {
	num int
}

func main() {
	a := A{
		num: 1,
	}
	arr1 := []*A{&a}
	arr2 := []A{a}
	defer func() {
 		fmt.Println((arr1[0]))
	}() // 1st defer
	defer fmt.Println(arr1[0]) // 2nd defer
	defer fmt.Println(arr2[0]) // 3rd defer

	arr1[0].num = 2
	arr2[0].num = 2
}

 

출력이 어떻게 될 것 같은가?

아마 함수의 인자 값이 defer를 실행할 때 정해지므로

뒤에 나온 arr1과 arr2의 num을 2로 바꿈과 상관 없이 num을 1로 가지고 있는 a 타입의 객체를 반환할 수 있다고 생각할 수 있다.

 

그러나 결과 값은 다음과 같다.

{1} // 3rd defer
&{2} // 2nd defer
&{2} // 1st defer

arr1을 출력한 함수들은 num이 2인 객체를 반환하였다!

그 이유는 arr1이 가지고 있는 값이 a 객체가 아니라 a 객체의 주소값이기 때문이다.

세번째 defer에서는 arr2가 가진 a 객체를 복사하여 출력을 한 것이고, 첫번째와 두번째 defer은 a 객체의 주소 값을 복사하였기 때문에 num이 2로 바뀐 a 객체를 출력한다.

 

이제 다음 예제를 보자.

 

package main

import "fmt"

type A struct {
    num int
}

func main() {
    a := A{
        num: 1,
    }
    arr1 := []*A{&a}
    arr2 := []A{a}
    defer func() {
        fmt.Println(*(arr1[0]))
    }() // 1st defer
    defer fmt.Println(*(arr1[0])) // 2nd defer
    defer fmt.Println(arr2[0]) // 3rd defer

    arr1[0].num = 2
    arr2[0].num = 2
}

위 예제와 다른 점은 arr1 출력시 reference 하는 객체를 불러온다.

해당 예제는

함수의 인자에 들어가는 값은 defer 실행시 계산이 되므로 1st defer, 2nd defer 모두 num이 1인 a 객체를 복사 받아 전부 num이 1인 a 객체를 출력했다고 착각하기 쉽다.

 

그러나 결과는 다음과 같다.

{1} // 3rd defer
{1} // 2nd defer
{2} // 1st defer

첫 번째 defer은 여전히 2를 출력한다!

그 이유는 첫번째 defer의 경우 함수의 인자에 arr1이 있는 것이 아닌 body에 arr1이 있기 때문이다.

함수가 call되는 시점은 defer를 감싸고 있는 함수가 return되기 직전이기에 arr1[0]이 가리키고 있는 객체의 num이 2가된 후에 call이 된다.

 

Comments