공대생 정리노트
defer - 실수하기 쉬운 인자 및 body의 값 계산 시점 본문
https://go.dev/doc/effective_go#defer
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이 된다.
'언어 > Go' 카테고리의 다른 글
Map : concurrent 상황일 때 Read도 RLock을 해야 하는 이유 (0) | 2022.02.18 |
---|---|
동시성 성능 테스트 mutex VS chan VS atomic (0) | 2021.12.19 |
파일 I/O - write과 read시 파일 lock이 있을까? (0) | 2021.12.08 |
Effective Go (2) 요약 (0) | 2021.11.01 |
Effective Go (1) 요약 (0) | 2021.10.18 |