疑问

  前面在函数篇里介绍了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的微信公众号,我会将我的文章推送给您,并和您一起分享我日常阅读过的优质文章。

Go语言学习——彻底弄懂return和defer的微妙关系的更多相关文章

  1. c语言学习之基础知识点介绍(五):关系运算式和逻辑运算式

    本节主要说关系运算式和逻辑运算式. 一.关系运算式 1.等于(==):判断左边的表达式是否等于右边的表达式 2.大于(>):判断左边的表达式是否大于右边的表达式 3.大于等于(>=):判断 ...

  2. Java学习——JSTL标签与EL表达式之间的微妙关系

    原文总结的太好了,忍不住记录.转发. 原文地址:http://blog.csdn.net/u010168160/article/details/49182867 目录(?)[-] 一EL表达式 EL相 ...

  3. 必须弄懂的495个C语言问题

    1.1 我如何决定使用那种整数类型? 如果需要大数 值(大于32, 767 或小于¡32, 767), 使用long 型.否则, 如果空间很重要(如有大数组或很多结构), 使用short 型.除此之外 ...

  4. 技能收获与C语言学习

    你有什么技能比大多人(超过90%以上)更好? 我会的东西很多,喜欢的东西太多,但是很遗憾广而不专,会而不精.学了很多东西我都是为了娱乐,因为以前我们那里过于强调学习,很多爱好也都被扼杀在摇篮里.我觉得 ...

  5. 技能学习经验与C语言学习调查

    技能学习经验与C语言学习调查 前言 要说的话,这还是我第一次写博客.不论是为了作业也好,为了将来的学习工作也好,写博客都是必不可少的,也算是个自我提升的途径吧.不过第一次写博客,就用从来没听说过的ma ...

  6. 20155229-付钰涵-分析自我技能延展到c语言学习状况

    我的小技能 我记得幼儿园时表演的舞蹈,也记得从水彩到素描的学习,还记得小学和初中获得的钢琴省级奖项. 舞蹈止于一年级,绘画止于三年级,钢琴从学前班到高一那十年的时间里有过断续. 03年-04年的那个冬 ...

  7. 20155306白皎 学习技能+C语言学习

    你有什么技能比大多数人更好 谈起技能,我还有感觉有微微拿得出手的也只有主持这一项才艺了吧.从小学到高中一直参加朗诵比赛,以及从小学到大学一直在所在学校有担任过主持工作. 上大学以来,也参加了院级朗诵比 ...

  8. 12天学好C语言——记录我的C语言学习之路(Day 8)

    12天学好C语言--记录我的C语言学习之路 Day 8: 从今天开始,我们获得了C语言中很有力的一个工具,那就是函数.函数的魅力不仅于此,一个程序到最后都是由众多函数组成的,我们一定要用好函数,用熟练 ...

  9. 12天学好C语言——记录我的C语言学习之路(Day 7)

    12天学好C语言--记录我的C语言学习之路 Day 7: 昨天进行了一天的数组学习,今天大家可以先写几个昨天的程序热热身,回顾回顾,然后今天第一个新程序也是关于数组的,比较难,准备好就开始啦! //输 ...

随机推荐

  1. MySQL - 常见的三种数据库存储引擎

    原文:MySQL - 常见的三种数据库存储引擎 数据库存储引擎:是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧. ...

  2. .NET开发人员的瓶颈和职业发展

    现在社会比前几年浮躁了,越来越多的人抱怨薪水低,高薪工作不好找; 诚然这有CPI的压力,可是也有很多人没有认清自己的职业发展. 很多.net程序员个各种纠结,想拿高薪又拿不到,想提高又不知道怎么能提高 ...

  3. php+mysql+nginx于linux部署对环境

    始终linux在补锅匠,在尚未完成linux根据 - 型nginxserver环境进行部署,这些天来,无论它是什么部署,遇到的问题非常多,今天,我的环境中部署文档发行,够一起讨论一下,希望大家採用后遇 ...

  4. 1 Quartz开始

    三个比较像的玩意儿 Quartz      Windows计划任务    timer(主要用到了线程池技术) quartz.net 是从java的quartz项目移植过来的  java版本 www.q ...

  5. Hopfield 神经网络及稳态性的证明

    根据其提出者,John Joseph Hopfield 命名.Hopfield 在 1982 年提出的划时代的:Neural networks and physical systems with em ...

  6. ios 拿到第一响应者的当前视图

    UIWindow *keyWindow = [[UIApplication sharedApplication] keyWindow]; UIView *firstResponder = [keyWi ...

  7. PYC文件简介

    PYC文件简介¶ 不说废话,这里说的pyc文件就是 Python 程序编译后得到的字节码文件 (py->pyc). 基本格式¶ pyc文件一般由3个部分组成: 最开始4个字节是一个Maigc i ...

  8. SQL like使用 模糊查询

    模糊查询: 参考资料:http://www.w3school.com.cn/sql/sql_wildcards.asp 在搜索数据库中的数据时,您能够使用 SQL 通配符. SQL 通配符  Like ...

  9. xmarin live player 连接 IOS以及安卓实现实时效果查看

    原文:xmarin live player 连接 IOS以及安卓实现实时效果查看 在之前有介绍过xamarin 单独IOS项目开发的运行环境搭建,但是这段时间我看到了xmarin forms 3.0  ...

  10. This problem will occur when running in 64 bit mode with the 32 bit Oracle client components installed.

    Attempt to load Oracle client libraries threw BadImageFormatException. This problem will occur when ...