疑问
前面在函数篇里介绍了 Go 语言的函数是支持多返回值的.
只要在函数体内, 对返回值赋值, 最后加上 return 就可以返回所有的返回值.
最近在写代码的时候经常遇到在 return 后, 还要在 defer 里面做一些收尾工作, 比如事务的提交或回滚. 所以想弄清楚这个 return 和 defer 到底是什么关系, 它们谁先谁后, 对于最后返回值又有什么影响呢?
动手验证
了解下来, 问题比我想的要复杂, 不信你先看看下面这段代码输出结果是啥
- package main
- import "fmt"
- func main() {
- fmt.Println("f1 result:", f1())
- fmt.Println("f2 result:", f2())
- }
- func f1() int {
- var i int
- defer func() {
- i++
- fmt.Println("f11:", i)
- }()
- defer func() {
- i++
- fmt.Println("f12:", i)
- }()
- i = 1000
- return i
- }
- func f2() (i int) {
- defer func() {
- i++
- fmt.Println("f21:", i)
- }()
- defer func() {
- i++
- fmt.Println("f22:", i)
- }()
- i = 1000
- return i
- }
最后的执行结果如下
- f12: 1001
- f11: 1002
- f1 result: 1000
- f22: 1001
- f21: 1002
- f2 result: 1002
f1 函数:
进入该函数, 因为没有指定返回值变量, 需要先声明 i 变量, 因为是 int 类型, 如果没有赋值, 该变量初始化值为 0, 之后执行 i=1000 的赋值操作, 然后执行 return 语句, 返回 i 的值.
真正返回之前还要执行 defer 函数部分, 两个 defer 函数分别针对 i 进行自增操作, i 的值依次为 1001 和 1002
f2 函数:
进入该函数, 因为已经定义好了返回值变量即为 i, 然后直接赋值 i=1000, 再返回 i 的值.
同样的, 也要在真正返回 i 前, 执行两个 defer 函数, 同样 i 依次自增得到 1001 和 1002.
问题的关键是为什么无名参数返回的值是 1000, 其并未收到 defer 函数对于 i 自增的影响; 而有名函数在执行 defer 后, 最后返回的 i 值为 1002.
网上找了一些原因, 提到一个结论
原因就是 return 会将返回值先保存起来, 对于无名返回值来说,
保存在一个临时对象中, defer 是看不到这个临时对象的;
而对于有名返回值来说, 就保存在已命名的变量中.
看到这个结论, 我想试试通过打印 i 的地址值是否可以看出一些端倪和线索
为此在两个函数中添加了打印 i 的地址信息
- package main
- import "fmt"
- func main() {
- fmt.Println("f1 result:", f1())
- fmt.Println("f2 result:", f2())
- }
- func f1() int {
- var i int
- fmt.Printf("i: %p \n", &i)
- defer func() {
- i++
- fmt.Printf("i: %p \n", &i)
- fmt.Println("f11:", i)
- }()
- defer func() {
- i++
- fmt.Printf("i: %p \n", &i)
- fmt.Println("f12:", i)
- }()
- i = 1000
- return i
- }
- func f2() (i int) {
- fmt.Printf("i: %p \n", &i)
- defer func() {
- i++
- fmt.Printf("i: %p \n", &i)
- fmt.Println("f21:", i)
- }()
- defer func() {
- i++
- fmt.Printf("i: %p \n", &i)
- fmt.Println("f22:", i)
- }()
- i = 1000
- return i
- }
程序输出结果为
- i: 0xc000090000
- i: 0xc000090000
- f12: 1001
- i: 0xc000090000
- f11: 1002
- f1 result: 1000
- i: 0xc00009a008
- i: 0xc00009a008
- f22: 1001
- i: 0xc00009a008
- f21: 1002
- f2 result: 1002
从这个结果可以看出, 无论是 f1 还是 f2 函数中, 变量 i 的地址全程没有改变过.
所以对于上面这个结论我似乎懂了, 但是还是有些模糊, return 保存在一个临时对象中, defer 看不到这个临时变量, 但是 i 的值为什么能够在 1000 的基础上累加呢?
拨开云雾
如果要从根本解决这个疑问, 最好能够看看这段程序执行, 背后的内存是如何分配的.
这时候想到了前几天看书里提到的可以通过命令将 go 语言转为汇编语言.
为了简化问题, 将源代码修改为
- package main
- import "fmt"
- func main() {
- fmt.Println("f1 result:", f1())
- fmt.Println("f2 result:", f2())
- }
- func f1() int {
- var i int
- defer func() {
- i++
- fmt.Println("f11:", i)
- }()
- i = 1000
- return i
- }
- func f2() (i int) {
- defer func() {
- i++
- fmt.Println("f21:", i)
- }()
- i = 1000
- return i
- }
通过执行命令 go tool compile -S test.go 得到汇编代码如下
- os.(*File).close STEXT dupok nosplit size=26 args=0x18 locals=0x0
- ...
- 0x0000 00000 (test.go:5) TEXT "".main(SB), ABIInternal, $136-0
- 0x0000 00000 (test.go:5) MOVQ (TLS), CX
- 0x0009 00009 (test.go:5) LEAQ -8(SP), AX
- 0x000e 00014 (test.go:5) CMPQ AX, 16(CX)
- 0x0012 00018 (test.go:5) JLS 315
- 0x0018 00024 (test.go:5) SUBQ $136, SP
- 0x001f 00031 (test.go:5) MOVQ BP, 128(SP)
- 0x0027 00039 (test.go:5) LEAQ 128(SP), BP
- 0x002f 00047 (test.go:5) FUNCDATA $0, gclocals.7d2d5fca80364273fb07d5820a76fef4(SB)
- ...
- "".f1 STEXT size=145 args=0x8 locals=0x28
- 0x0000 00000 (test.go:10) TEXT "".f1(SB), ABIInternal, $40-8
- 0x0000 00000 (test.go:10) MOVQ (TLS), CX
- 0x0009 00009 (test.go:10) CMPQ SP, 16(CX)
- 0x000d 00013 (test.go:10) JLS 135
- 0x000f 00015 (test.go:10) SUBQ $40, SP
- 0x0013 00019 (test.go:10) MOVQ BP, 32(SP)
- 0x0018 00024 (test.go:10) LEAQ 32(SP), BP
- 0x001d 00029 (test.go:10) FUNCDATA $0, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:10) FUNCDATA $1, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:10) FUNCDATA $3, gclocals.9fb7f0986f647f17cb53dda1484e0f7a(SB)
- 0x001d 00029 (test.go:10) PCDATA $2, $0
- 0x001d 00029 (test.go:10) PCDATA $0, $0
- 0x001d 00029 (test.go:10) MOVQ $0, "".~r0+48(SP)
- 0x0026 00038 (test.go:11) MOVQ $0, "".i+24(SP)
- 0x002f 00047 (test.go:12) MOVL $8, (SP)
- 0x0036 00054 (test.go:12) PCDATA $2, $1
- 0x0036 00054 (test.go:12) LEAQ "".f1.func1.f(SB), AX
- 0x003d 00061 (test.go:12) PCDATA $2, $0
- 0x003d 00061 (test.go:12) MOVQ AX, 8(SP)
- 0x0042 00066 (test.go:12) PCDATA $2, $1
- 0x0042 00066 (test.go:12) LEAQ "".i+24(SP), AX
- 0x0047 00071 (test.go:12) PCDATA $2, $0
- 0x0047 00071 (test.go:12) MOVQ AX, 16(SP)
- 0x004c 00076 (test.go:12) CALL runtime.deferproc(SB)
- 0x0051 00081 (test.go:12) TESTL AX, AX
- 0x0053 00083 (test.go:12) JNE 119
- 0x0055 00085 (test.go:17) MOVQ $1000, "".i+24(SP)
- 0x005e 00094 (test.go:18) MOVQ $1000, "".~r0+48(SP)
- 0x0067 00103 (test.go:18) XCHGL AX, AX
- 0x0068 00104 (test.go:18) CALL runtime.deferreturn(SB)
- 0x006d 00109 (test.go:18) MOVQ 32(SP), BP
- 0x0072 00114 (test.go:18) ADDQ $40, SP
- 0x0076 00118 (test.go:18) RET
- 0x0077 00119 (test.go:12) XCHGL AX, AX
- 0x0078 00120 (test.go:12) CALL runtime.deferreturn(SB)
- 0x007d 00125 (test.go:12) MOVQ 32(SP), BP
- 0x0082 00130 (test.go:12) ADDQ $40, SP
- 0x0086 00134 (test.go:12) RET
- 0x0087 00135 (test.go:12) NOP
- 0x0087 00135 (test.go:10) PCDATA $0, $-1
- 0x0087 00135 (test.go:10) PCDATA $2, $-1
- 0x0087 00135 (test.go:10) CALL runtime.morestack_noctxt(SB)
- 0x008c 00140 (test.go:10) JMP 0
- ...
- 0x0000 00000 (test.go:21) TEXT "".f2(SB), ABIInternal, $32-8
- 0x0000 00000 (test.go:21) MOVQ (TLS), CX
- 0x0009 00009 (test.go:21) CMPQ SP, 16(CX)
- 0x000d 00013 (test.go:21) JLS 117
- 0x000f 00015 (test.go:21) SUBQ $32, SP
- 0x0013 00019 (test.go:21) MOVQ BP, 24(SP)
- 0x0018 00024 (test.go:21) LEAQ 24(SP), BP
- 0x001d 00029 (test.go:21) FUNCDATA $0, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:21) FUNCDATA $1, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:21) FUNCDATA $3, gclocals.9fb7f0986f647f17cb53dda1484e0f7a(SB)
- 0x001d 00029 (test.go:21) PCDATA $2, $0
- 0x001d 00029 (test.go:21) PCDATA $0, $0
- 0x001d 00029 (test.go:21) MOVQ $0, "".i+40(SP)
- 0x0026 00038 (test.go:22) MOVL $8, (SP)
- 0x002d 00045 (test.go:22) PCDATA $2, $1
- 0x002d 00045 (test.go:22) LEAQ "".f2.func1.f(SB), AX
- 0x0034 00052 (test.go:22) PCDATA $2, $0
- 0x0034 00052 (test.go:22) MOVQ AX, 8(SP)
- 0x0039 00057 (test.go:22) PCDATA $2, $1
- 0x0039 00057 (test.go:22) LEAQ "".i+40(SP), AX
- 0x003e 00062 (test.go:22) PCDATA $2, $0
- 0x003e 00062 (test.go:22) MOVQ AX, 16(SP)
- 0x0043 00067 (test.go:22) CALL runtime.deferproc(SB)
- 0x0048 00072 (test.go:22) TESTL AX, AX
- 0x004a 00074 (test.go:22) JNE 101
- 0x004c 00076 (test.go:26) MOVQ $1000, "".i+40(SP)
- 0x0055 00085 (test.go:27) XCHGL AX, AX
- 0x0056 00086 (test.go:27) CALL runtime.deferreturn(SB)
- 0x005b 00091 (test.go:27) MOVQ 24(SP), BP
- 0x0060 00096 (test.go:27) ADDQ $32, SP
- 0x0064 00100 (test.go:27) RET
- 0x0065 00101 (test.go:22) XCHGL AX, AX
- 0x0066 00102 (test.go:22) CALL runtime.deferreturn(SB)
- 0x006b 00107 (test.go:22) MOVQ 24(SP), BP
- 0x0070 00112 (test.go:22) ADDQ $32, SP
- 0x0074 00116 (test.go:22) RET
- 0x0075 00117 (test.go:22) NOP
- 0x0075 00117 (test.go:21) PCDATA $0, $-1
- 0x0075 00117 (test.go:21) PCDATA $2, $-1
- 0x0075 00117 (test.go:21) CALL runtime.morestack_noctxt(SB)
- 0x007a 00122 (test.go:21) JMP 0
- ... ........
- rel 16+8 t=1 type.[2]interface {}+0
感觉离真相只差一步了, 就是看完这段汇编代码就能搞明白这个 return 在无名和有名返回值时分别做了什么, 所谓的零时变量是咋分配的, 想想就有点小激动呢
但是, 比较棘手的是, 我没学过汇编 -_-!
但是 again, 这有什么关系呢, 两个函数既然执行结果不一样, 那么在汇编层面肯定也有不一样的地方, 于是开始找不同, 最终在上面的汇编代码分别找到关键信息如下
- "".f2 STEXT size=124 args=0x8 locals=0x20
- 0x0000 00000 (test.go:21) TEXT "".f2(SB), ABIInternal, $32-8
- 0x0000 00000 (test.go:21) MOVQ (TLS), CX
- 0x0009 00009 (test.go:21) CMPQ SP, 16(CX)
- 0x000d 00013 (test.go:21) JLS 117
- 0x000f 00015 (test.go:21) SUBQ $32, SP
- 0x0013 00019 (test.go:21) MOVQ BP, 24(SP)
- 0x0018 00024 (test.go:21) LEAQ 24(SP), BP
- 0x001d 00029 (test.go:21) FUNCDATA $0, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:21) FUNCDATA $1, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:21) FUNCDATA $3, gclocals.9fb7f0986f647f17cb53dda1484e0f7a(SB)
- 0x001d 00029 (test.go:21) PCDATA $2, $0
- 0x001d 00029 (test.go:21) PCDATA $0, $0
- 0x001d 00029 (test.go:21) MOVQ $0, "".i+40(SP)
- 0x0026 00038 (test.go:22) MOVL $8, (SP)
- 0x002d 00045 (test.go:22) PCDATA $2, $1
- 0x002d 00045 (test.go:22) LEAQ "".f2.func1.f(SB), AX
- 0x0034 00052 (test.go:22) PCDATA $2, $0
- 0x0034 00052 (test.go:22) MOVQ AX, 8(SP)
- 0x0039 00057 (test.go:22) PCDATA $2, $1
- 0x0039 00057 (test.go:22) LEAQ "".i+40(SP), AX
- 0x003e 00062 (test.go:22) PCDATA $2, $0
- 0x003e 00062 (test.go:22) MOVQ AX, 16(SP)
- 0x0043 00067 (test.go:22) CALL runtime.deferproc(SB)
- 0x0048 00072 (test.go:22) TESTL AX, AX
- 0x004a 00074 (test.go:22) JNE 101
- 0x004c 00076 (test.go:26) MOVQ $1000, "".i+40(SP)
- 0x0055 00085 (test.go:27) XCHGL AX, AX
- 0x0056 00086 (test.go:27) CALL runtime.deferreturn(SB)
- 0x005b 00091 (test.go:27) MOVQ 24(SP), BP
- 0x0060 00096 (test.go:27) ADDQ $32, SP
- 0x0064 00100 (test.go:27) RET
- 0x0065 00101 (test.go:22) XCHGL AX, AX
- 0x0066 00102 (test.go:22) CALL runtime.deferreturn(SB)
- 0x006b 00107 (test.go:22) MOVQ 24(SP), BP
- 0x0070 00112 (test.go:22) ADDQ $32, SP
- 0x0074 00116 (test.go:22) RET
- 0x0075 00117 (test.go:22) NOP
- 0x0075 00117 (test.go:21) PCDATA $0, $-1
- 0x0075 00117 (test.go:21) PCDATA $2, $-1
- 0x0075 00117 (test.go:21) CALL runtime.morestack_noctxt(SB)
- 0x007a 00122 (test.go:21) JMP 0
这是 f2 有名返回值的关键信息, 主要看
0x004c 00076 (test.go:26) MOVQ $1000, "".i+40(SP)
这个大概意思就是把 1000 放到 "".i+40(SP) 这个内存地址上, 然后下面执行的操作就是返回了
- "".f1 STEXT size=145 args=0x8 locals=0x28
- 0x0000 00000 (test.go:10) TEXT "".f1(SB), ABIInternal, $40-8
- 0x0000 00000 (test.go:10) MOVQ (TLS), CX
- 0x0009 00009 (test.go:10) CMPQ SP, 16(CX)
- 0x000d 00013 (test.go:10) JLS 135
- 0x000f 00015 (test.go:10) SUBQ $40, SP
- 0x0013 00019 (test.go:10) MOVQ BP, 32(SP)
- 0x0018 00024 (test.go:10) LEAQ 32(SP), BP
- 0x001d 00029 (test.go:10) FUNCDATA $0, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:10) FUNCDATA $1, gclocals.33cdeccccebe80329f1fdbee7f5874cb(SB)
- 0x001d 00029 (test.go:10) FUNCDATA $3, gclocals.9fb7f0986f647f17cb53dda1484e0f7a(SB)
- 0x001d 00029 (test.go:10) PCDATA $2, $0
- 0x001d 00029 (test.go:10) PCDATA $0, $0
- 0x001d 00029 (test.go:10) MOVQ $0, "".~r0+48(SP)
- 0x0026 00038 (test.go:11) MOVQ $0, "".i+24(SP)
- 0x002f 00047 (test.go:12) MOVL $8, (SP)
- 0x0036 00054 (test.go:12) PCDATA $2, $1
- 0x0036 00054 (test.go:12) LEAQ "".f1.func1.f(SB), AX
- 0x003d 00061 (test.go:12) PCDATA $2, $0
- 0x003d 00061 (test.go:12) MOVQ AX, 8(SP)
- 0x0042 00066 (test.go:12) PCDATA $2, $1
- 0x0042 00066 (test.go:12) LEAQ "".i+24(SP), AX
- 0x0047 00071 (test.go:12) PCDATA $2, $0
- 0x0047 00071 (test.go:12) MOVQ AX, 16(SP)
- 0x004c 00076 (test.go:12) CALL runtime.deferproc(SB)
- 0x0051 00081 (test.go:12) TESTL AX, AX
- 0x0053 00083 (test.go:12) JNE 119
- 0x0055 00085 (test.go:17) MOVQ $1000, "".i+24(SP)
- 0x005e 00094 (test.go:18) MOVQ $1000, "".~r0+48(SP)
- 0x0067 00103 (test.go:18) XCHGL AX, AX
- 0x0068 00104 (test.go:18) CALL runtime.deferreturn(SB)
- 0x006d 00109 (test.go:18) MOVQ 32(SP), BP
- 0x0072 00114 (test.go:18) ADDQ $40, SP
- 0x0076 00118 (test.go:18) RET
- 0x0077 00119 (test.go:12) XCHGL AX, AX
- 0x0078 00120 (test.go:12) CALL runtime.deferreturn(SB)
- 0x007d 00125 (test.go:12) MOVQ 32(SP), BP
- 0x0082 00130 (test.go:12) ADDQ $40, SP
- 0x0086 00134 (test.go:12) RET
- 0x0087 00135 (test.go:12) NOP
- 0x0087 00135 (test.go:10) PCDATA $0, $-1
- 0x0087 00135 (test.go:10) PCDATA $2, $-1
- 0x0087 00135 (test.go:10) CALL runtime.morestack_noctxt(SB)
- 0x008c 00140 (test.go:10) JMP 0
这是 f1 无名返回值的关键信息, 主要看
- 0x0055 00085 (test.go:17) MOVQ $1000, "".i+24(SP)
- 0x005e 00094 (test.go:18) MOVQ $1000, "".~r0+48(SP)
这个大概意思就是把 1000 放到 "".i+24(SP) 这个内存地址上, 然后又把 1000 赋给了"".~r0+48(SP), 这就是和 f1 不一样的地方. 对应前面结论, 我们在这里找到了验证. 大致过程就是无名返回值的情况, 在 return 的时候开辟了一个新内存空间, 后续的 defer 读取的还是 "".i+24(SP) 这样的内存地址而无法读取临时空间的值. return 在函数最后返回的也是"".~r0+48(SP) 对应的值即 1000.(因为没有研究过汇编, 有些细节可能有待考证)
结论
到此, 我们算是搞明白了 Go 语言里面 return 和 defer 之间的微妙关系, 从汇编层面看清了在无名返回值和有名返回值 return 返回的差异.
如果您觉得阅读本文对您有帮助, 请点一下 "推荐" 按钮, 您的 "推荐" 将是我最大的写作动力! 如果您想持续关注我的文章, 请扫描二维码, 关注 JackieZheng 的微信公众号, 我会将我的文章推送给您, 并和您一起分享我日常阅读过的优质文章.
来源: https://www.cnblogs.com/bigdataZJ/p/return-and-defer.html