飞道的博客

Golang汇编之defer的执行

377人阅读  评论(0)

@ 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.linkdeferreturn()会不断被调用,直到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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场