在上一章《深入理解 Go panic and recover》中,我们发现defer跟它有很大的关系,我们还是觉得有必要深入了解一下。希望通过本章,你可以对defer关键字有一个深刻的理解,那么让我们开始吧。请稍等,请排队。这里采用后进先出的出站方式...原文地址:深入理解Godefer特性。下面简单回顾一下defer关键字的基本使用,让大家先有个基础。认知1.延迟调用funcmain(){deferlog.Println("EDDYCJY.")log.Println("end.")}输出结果:$gorunmain.go2019/05/1921:15:02end.2019/05/1921:15:02EDDYCJY。2.LIFOfuncmain(){fori:=0;我<6;i++{deferlog.Println("EDDYCJY"+strconv.Itoa(i)+".")}log.Println("end.")}输出:$gorunmain.go2019/05/1921:19:17end.2019/05/1921:19:17EDDYCJY5.2019/05/1921:19:17EDDYCJY4.2019/05/1921:19:17EDDYCJY3.2019/05/1921:19:17EDDYCJY2.2019/05/1921:19:17EDDYCJY1.2019/05/1921:19:17EDDYCJY0。3.运行时间funcmain(){func(){deferlog.Println("defer.EDDYCJY.")}()log.Println("main.EDDYCJY.")}输出结果:$gorunmain.go2019/05/2223:30:27defer.EDDYCJY.2019/05/2223:30:27main.EDDYCJY。4.异常处理funcmain(){deferfunc(){ife:=recover();e!=nil{log.Println("EDDYCJY.")}}()panic("end.")}输出结果:$gorunmain.go2019/05/2022:22:57EDDYCJY。源码分析$gotoolcompile-Smain.go"".mainSTEXTsize=163args=0x0locals=0x40...0x005900089(main.go:6)MOVQAX,16(SP)0x005e00094(main.go:6)MOVQ$1,24(SP)0x006700103(main.go:6)MOVQ$1,32(SP)0x007000112(main.go:6)CALLruntime.deferproc(SB)0x007500117(main.go:6)TESTLAX,AX0x007700119(main.go:6)JNE1370x007900121(main.go:7)XCHGLAX,AX0x007a00122(main.go:7)CALLruntime.deferreturn(SB)0x007f00127(main..go:7)MOVQ56(SP),BP0x008400132(main.go:7)ADDQ$64,SP0x008800136(main.go:7)RET0x008900137(main.go:6)XCHGLAX,AX0x008a00138(main.go:6)CALLruntime.deferreturn(SB)0x008f00143(main.go:6)MOVQ56(SP),BP0x009400148(main.go:6)ADDQ$64,SP0x009800152(main.go:6)RET...首先我们要找到它,找出它实际对应的是什么执行代码通过汇编代码可以看出,涉及到以下几个方法:我们继续往下看他们分别进行了哪些行为。数据结构在开始之前,我们需要先介绍一下defer的基本单元,即_defer结构体,如下:panic正在运行deferlink*_defer}...typefuncvalstruct{fnuintptr//可变大小,此处为fn特定数据}size:所有传入参数的总大小started:是否已执行defersp:函数堆栈指针寄存器,一般指向当前函数栈顶pc:程序计数器,有时称为指令指针(IP),线程用它来跟踪下一条要执行的指令。在大多数处理器中,PC指向下一条指令,而不是当前指令fn:指向传入的函数地址和参数_panic:指向_panic链表link:指向_defer链表deferprocfuncdeferproc(sizint32,fn*funcval){...sp:=getcallersp()argp:=uintptr(unsafe.Pointer(&fn))+unsafe。Sizeof(fn)callerpc:=getcallerpc()d:=newdefer(siz)...d.fn=fnd.pc=callerpcd.sp=spswitchsiz{case0://什么都不做。casesys.PtrSize:*(*uintptr)(deferArgs(d))=*(*uintptr)(unsafe.Pointer(argp))默认值:memmove(deferArgs(d),unsafe.Pointer(argp),uintptr(siz))}return0()}得到调用defer函数的函数栈指针,函数传入参数的具体地址和PC(程序计数器),也就是下一条要执行的指令。这些相当于为后续流控准备参数创建一个新的defer最小单元_defer,将之前准备的参数填入并调用memmove将传入的参数存储到新的_defer(当前使用)中,以供后续使用最后调用return0返回,这个功能非常重要。可以避免因为返回return而调用deferproc中的deferreturn方法。其根本原因是一个停止panic的defer方法会让deferproc返回1,但在机制上如果deferproc返回不等于0,它会一直检查返回值并跳转到函数的末尾。而return0返回0,所以可以防止重复调用。总结在这个函数中,会为新的_defer设置一些基本属性,传入调用函数的参数集。最后,函数调用由一个特殊的返回方法终止。另外,这部分和前面《深入理解 Go panic and recover》的处理逻辑有关。事实上,无论gp.sched.ret返回0还是1,都会转向不同的处理方法sc
