@ Golang汇编之defer的执行
简单的现象
defer 语句会将其后面跟随的语句进行延迟处理,在 defer 归属的函数即将返回时,将延迟处理的语句按 defer 的逆序进行执行,也就是说,先被 defer 的语句最后被执行,最后被 defer 的语句,最先被执行。
test.go
package main
import (
"fmt"
)
func main() {
defer func() {
fmt.Print(1)
}()
defer func() {
fmt.Print(2)
}()
}
// output:21
那为什么是这样呢?
正文
老规矩,输出下汇编(有删节,但无影响)
0x0000 00000 (.\test.go:7) TEXT "".main(SB), ABIInternal, $176-0
0x0000 00000 (.\test.go:7) MOVQ TLS, CX
0x0009 00009 (.\test.go:7) PCDATA $0, $-2
0x0009 00009 (.\test.go:7) MOVQ (CX)(TLS*2), CX
0x0010 00016 (.\test.go:7) PCDATA $0, $-1
0x0010 00016 (.\test.go:7) LEAQ -48(SP), AX
0x0015 00021 (.\test.go:7) CMPQ AX, 16(CX)
0x0019 00025 (.\test.go:7) PCDATA $0, $-2
0x0019 00025 (.\test.go:7) JLS 205
0x001f 00031 (.\test.go:7) PCDATA $0, $-1
0x001f 00031 (.\test.go:7) SUBQ $176, SP
0x0026 00038 (.\test.go:7) MOVQ BP, 168(SP)
0x002e 00046 (.\test.go:7) LEAQ 168(SP), BP
0x0036 00054 (.\test.go:7) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0036 00054 (.\test.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0036 00054 (.\test.go:8) MOVL $0, ""..autotmp_0+88(SP)
0x003e 00062 (.\test.go:8) LEAQ "".main.func1·f(SB), AX
0x0045 00069 (.\test.go:8) MOVQ AX, ""..autotmp_0+112(SP)
0x004a 00074 (.\test.go:8) LEAQ ""..autotmp_0+88(SP), AX
0x004f 00079 (.\test.go:8) MOVQ AX, (SP)
0x0053 00083 (.\test.go:8) PCDATA $1, $0
0x0053 00083 (.\test.go:8) CALL runtime.deferprocStack(SB)
0x0058 00088 (.\test.go:8) TESTL AX, AX
0x005a 00090 (.\test.go:8) JNE 183
0x005c 00092 (.\test.go:8) JMP 94
0x005e 00094 (.\test.go:11) MOVL $0, ""..autotmp_1+8(SP)
0x0066 00102 (.\test.go:11) LEAQ "".main.func2·f(SB), AX
0x006d 00109 (.\test.go:11) MOVQ AX, ""..autotmp_1+32(SP)
0x0072 00114 (.\test.go:11) LEAQ ""..autotmp_1+8(SP), AX
0x0077 00119 (.\test.go:11) MOVQ AX, (SP)
0x007b 00123 (.\test.go:11) NOP
0x0080 00128 (.\test.go:11) CALL runtime.deferprocStack(SB)
0x0085 00133 (.\test.go:11) TESTL AX, AX
0x0087 00135 (.\test.go:11) JNE 161
0x0089 00137 (.\test.go:11) JMP 139
0x008b 00139 (.\test.go:14) XCHGL AX, AX
0x008c 00140 (.\test.go:14) CALL runtime.deferreturn(SB)
0x0091 00145 (.\test.go:14) MOVQ 168(SP), BP
0x0099 00153 (.\test.go:14) ADDQ $176, SP
0x00a0 00160 (.\test.go:14) RET
0x00a1 00161 (.\test.go:11) XCHGL AX, AX
0x00a2 00162 (.\test.go:11) CALL runtime.deferreturn(SB)
0x00a7 00167 (.\test.go:11) MOVQ 168(SP), BP
0x00af 00175 (.\test.go:11) ADDQ $176, SP
0x00b6 00182 (.\test.go:11) RET
0x00b7 00183 (.\test.go:8) XCHGL AX, AX
0x00b8 00184 (.\test.go:8) CALL runtime.deferreturn(SB)
0x00bd 00189 (.\test.go:8) MOVQ 168(SP), BP
0x00c5 00197 (.\test.go:8) ADDQ $176, SP
0x00cc 00204 (.\test.go:8) RET
runtime.deferprocStack()
runtime.dederreturn()
不出意外和这两个函数有关联,源码我就不贴上来了,自行查看,我大致讲一下
https://github.com/golang/go/blob/master/src/runtime/panic.go
deferprocStack(d *_defer) // deferprocStack queues a new deferred function with a defer record on the stack
_defer
记录了一个延时函数的信息,具体信息自行查看源码,这里不做阐述。
deferprocStack()
大致是先使d.link
指向Goroutine._defer
然后使Goroutine._defer
指向d
,构成单向链表,Goroutine._defer
最终指向链表的最后一个元素,第一个d.link
指向nil
,这样就构成了延时函数栈。
deferreturn(arg0 uintptr) // is called again and again until there are no more deferred functions.
deferreturn()
会通过执行runOpenDeferFrame()
执行延时函数,通过freedefer()
和jmpdefer()
释放掉当前Goroutine._defer
所指向的_defer
,也会使Goroutine._defer
指向Goroutine._defer.link
。deferreturn()
会不断被调用,直到Goroutine._defer
指向nil
。
加个餐
在煎鱼的公众号上看到的,哈哈
test.go
package main
func f1() (r int) {
defer func() {
r++
}()
return 0
}
func f2() (r int) {
t := 5
defer func() {
t = t + 5
}()
return t
}
func f3() (r int) {
defer func(r int) {
r = r + 5
}(r)
return 1
}
func main() {
println(f1())
println(f2())
println(f3())
}
output:
1
5
1
老规矩,输出下汇编(有删节,但无影响)
f1()
"".f1 STEXT size=146 args=0x8 locals=0x60 funcid=0x0
0x0000 00000 (.\test.go:3) TEXT "".f1(SB), ABIInternal, $96-8 // 可以知道返回值r存放的区域应该是104SP
0x0000 00000 (.\test.go:3) MOVQ TLS, CX
0x0009 00009 (.\test.go:3) PCDATA $0, $-2
0x0009 00009 (.\test.go:3) MOVQ (CX)(TLS*2), CX
0x0010 00016 (.\test.go:3) PCDATA $0, $-1
0x0010 00016 (.\test.go:3) CMPQ SP, 16(CX)
0x0014 00020 (.\test.go:3) PCDATA $0, $-2
0x0014 00020 (.\test.go:3) JLS 136
0x0016 00022 (.\test.go:3) PCDATA $0, $-1
0x0016 00022 (.\test.go:3) SUBQ $96, SP
0x001a 00026 (.\test.go:3) MOVQ BP, 88(SP)
0x001f 00031 (.\test.go:3) LEAQ 88(SP), BP
0x0024 00036 (.\test.go:3) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0024 00036 (.\test.go:3) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0024 00036 (.\test.go:3) MOVQ $0, "".r+104(SP) // 首先初始化r 赋值为0
0x002d 00045 (.\test.go:4) MOVL $8, ""..autotmp_1+8(SP)
0x0035 00053 (.\test.go:4) LEAQ "".f1.func1·f(SB), AX
0x003c 00060 (.\test.go:4) MOVQ AX, ""..autotmp_1+32(SP)
0x0041 00065 (.\test.go:4) LEAQ "".r+104(SP), AX
0x0046 00070 (.\test.go:4) MOVQ AX, ""..autotmp_1+80(SP)
0x004b 00075 (.\test.go:4) LEAQ ""..autotmp_1+8(SP), AX
0x0050 00080 (.\test.go:4) MOVQ AX, (SP)
0x0054 00084 (.\test.go:4) PCDATA $1, $0
0x0054 00084 (.\test.go:4) CALL runtime.deferprocStack(SB) // 将延时函数压入栈
0x0059 00089 (.\test.go:4) TESTL AX, AX
0x005b 00091 (.\test.go:4) JNE 120
0x005d 00093 (.\test.go:4) JMP 95
0x005f 00095 (.\test.go:7) MOVQ $0, "".r+104(SP) // 结合代码这里是给r赋值为0,以此推测在函数返回前,会将return 后面的表达式或者变量的值写入返回值对应的内存空间
0x0068 00104 (.\test.go:7) XCHGL AX, AX
0x0069 00105 (.\test.go:7) CALL runtime.deferreturn(SB) // 开始执行延时函数
0x006e 00110 (.\test.go:7) MOVQ 88(SP), BP
0x0073 00115 (.\test.go:7) ADDQ $96, SP
0x0077 00119 (.\test.go:7) RET // 返回
0x0078 00120 (.\test.go:4) XCHGL AX, AX
0x0079 00121 (.\test.go:4) CALL runtime.deferreturn(SB)
0x007e 00126 (.\test.go:4) MOVQ 88(SP), BP
0x0083 00131 (.\test.go:4) ADDQ $96, SP
0x0087 00135 (.\test.go:4) RET
// f1.func1 就是 defer 执行的匿名函数
"".f1.func1 STEXT nosplit size=20 args=0x8 locals=0x0 funcid=0x0
0x0000 00000 (.\test.go:4) TEXT "".f1.func1(SB), NOSPLIT|ABIInternal, $0-8 // 虽然在函数定义时没有传参和返回值,但是从argsize的值可以看出,有参数的传递,根据代码结合来看就是r
0x0000 00000 (.\test.go:4) FUNCDATA $0, gclocals·1a65e721a2ccc325b382662e7ffee780(SB)
0x0000 00000 (.\test.go:4) FUNCDATA $1, gclocals·69c1753bd5f81501d95132d08af04464(SB)
0x0000 00000 (.\test.go:5) MOVQ "".&r+8(SP), AX // 将r的地址赋值给AX,即AX指向了r
0x0005 00005 (.\test.go:5) MOVQ (AX), AX // 取AX所指向的值赋给AX,即AX=0
0x0008 00008 (.\test.go:5) MOVQ "".&r+8(SP), CX // 将r的地址赋值给CX,即CX指向了r
0x000d 00013 (.\test.go:5) INCQ AX // AX自加 ,即AX=1
0x0010 00016 (.\test.go:5) MOVQ AX, (CX) //将AX的值赋值给r
0x0013 00019 (.\test.go:6) RET // 返回
可见,在延时函数执行前会存在一个赋值过程,即将return
后面的表达式或者变量,写到返回值的内存空间里,而延时函数如果在执行过程中修改了返回值的内存空间的数值,就会使得return
后面的表达式或者变量失去作用。
如果有帮助的话,可以点个赞,喜欢的话可以收藏,博客写的少,快要面试了,想有点东西拿出手,哈哈哈哈,最近这段时间会把一些遇见东西都写出来,所学的也会分享一下
转载:https://blog.csdn.net/qq_17818281/article/details/114897384