// A _defer holds an entry on the list of deferred calls. // If you add a field here, add code to clear it in freedefer and deferProcStack // This struct must match the code in cmd/compile/internal/reflectdata/reflect.go:deferstruct // and cmd/compile/internal/gc/ssa.go:(*state).call. // Some defers will be allocated on the stack and some on the heap. // All defers are logically part of the stack, so write barriers to // initialize them are not required. All defers must be manually scanned, // and for heap defers, marked. type _defer struct { siz int32// includes both arguments and results started bool heap bool // openDefer indicates that this _defer is for a frame with open-coded // defers. We have only one defer record for the entire frame (which may // currently have 0, 1, or more defers active). openDefer bool sp uintptr// sp at time of defer pc uintptr// pc at time of defer fn *funcval // can be nil for open-coded defers _panic *_panic // panic that is running defer link *_defer
// If openDefer is true, the fields below record values about the stack // frame and associated function that has the open-coded defer(s). sp // above will be the sp for the frame, and pc will be address of the // deferreturn call in the function. fd unsafe.Pointer // funcdata for the function associated with the frame varp uintptr// value of varp for the stack frame // framepc is the current pc associated with the stack frame. Together, // with sp above (which is the sp associated with the stack frame), // framepc/sp can be used as pc/sp pair to continue a stack trace via // gentraceback(). framepc uintptr }
defer 结构本身不复杂,其中的字段看名字就很好理解,这里说几个要点,后面会用到的
Some defers will be allocated on the stack and some on the heap 注释中有这样一句话,并且还有一个字段 heap 专门用于标识是分配在栈上还是堆上
siz 字段后面的注释说的很清楚,这里表示的 size 是包含参数和结果的,也就是说参数和返回值的空间一开始就分配好了
link 字段就是串联了整个 defer 链表,因为我们的 defer 是可以使用多次的,而 defer 的顺序就是通过这个字段串起来的
defer 实现
现在我们知道了 defer 的样子,就来看看它编译之后是怎么样实现的吧
原函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
package main
funcmain() { println(a()) }
funca()int { x := 123 defer defFun(x) return x }
funcdefFun(a int) { a++ }
生成汇编
go tool compile -S -N -l main.go>> main.md
main 函数就是直接调用了 a 函数,故 main 里面的我们就不看了,直接看 a 函数里面究竟做了什么事情
funcdeferprocStack(d *_defer) { gp := getg() if gp.m.curg != gp { // go code on the system stack can't defer throw("defer on system stack") } if goexperiment.RegabiDefer && d.siz != 0 { throw("defer with non-empty frame") } // siz and fn are already set. // The other fields are junk on entry to deferprocStack and // are initialized here. d.started = false d.heap = false d.openDefer = false d.sp = getcallersp() d.pc = getcallerpc() d.framepc = 0 d.varp = 0 // The lines below implement: // d.panic = nil // d.fd = nil // d.link = gp._defer // gp._defer = d // But without write barriers. The first three are writes to // the stack so they don't need a write barrier, and furthermore // are to uninitialized memory, so they must not use a write barrier. // The fourth write does not require a write barrier because we // explicitly mark all the defer structures, so we don't need to // keep track of pointers to them with a write barrier. *(*uintptr)(unsafe.Pointer(&d._panic)) = 0 *(*uintptr)(unsafe.Pointer(&d.fd)) = 0 // 这里就是 defer 串联的地方,首先将自己的 link 字段赋值为当前 g 的 _defer,然后将当前 g 的 _defer 赋值为自己 // 假设当前为 g.defer = a ; a.link = b; 现在的是 c // 则 c.link = a ; g.defer = c ; a.link = b; 从而串起来了。 // 其实这也就是为什么 defer 是先进后出的原因 *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) *(*uintptr)(unsafe.Pointer(&gp._defer)) = uintptr(unsafe.Pointer(d))
return0() // No code can go here - the C return register has // been set and must not be clobbered. }
funcdeferreturn() { gp := getg() d := gp._defer // 如果当前没有 defer 了,直接返回 if d == nil { return } // 获取 sp 进行比较 sp := getcallersp() // 如果与当前的 sp 不一致,那么证明这个 defer 的调用函数并不是这个,直接返回 if d.sp != sp { return } // openDefer 的问题后面我们再详细说 if d.openDefer { done := runOpenDeferFrame(gp, d) if !done { throw("unfinished open-coded defers in deferreturn") } gp._defer = d.link freedefer(d) return }
// 获取执行 fn 所需要的参数 // Moving arguments around. // // Everything called after this point must be recursively // nosplit because the garbage collector won't know the form // of the arguments until the jmpdefer can flip the PC over to // fn. argp := getcallersp() + sys.MinFrameSize switch d.siz { case0: // Do nothing. case sys.PtrSize: *(*uintptr)(unsafe.Pointer(argp)) = *(*uintptr)(deferArgs(d)) default: memmove(unsafe.Pointer(argp), deferArgs(d), uintptr(d.siz)) } fn := d.fn d.fn = nil // 相当于出栈了一个 defer gp._defer = d.link // 释放对应的空间 freedefer(d) // If the defer function pointer is nil, force the seg fault to happen // here rather than in jmpdefer. gentraceback() throws an error if it is // called with a callback on an LR architecture and jmpdefer is on the // stack, because the stack trace can be incorrect in that case - see // issue #8153). _ = fn.fn // 执行 defer 需要执行的函数 jmpdefer(fn, argp) }
最重要的就是最后一个方法 jmpdefer
1 2 3 4 5 6 7 8 9 10 11
TEXT runtime·jmpdefer(SB), NOSPLIT, $0-8 MOVL fv+0(FP), DX // fn MOVL argp+4(FP), BX // caller sp LEAL -4(BX), SP // caller sp after CALL #ifdef GOBUILDMODE_shared SUBL $16, (SP) // return to CALL again #else SUBL $5, (SP) // return to CALL again #endif MOVL 0(DX), BX JMP BX // but first run the deferred function
funcdeferproc(siz int32, fn *funcval) { // arguments of fn follow fn gp := getg() if gp.m.curg != gp { // go code on the system stack can't defer throw("defer on system stack") }
if goexperiment.RegabiDefer && siz != 0 { // TODO: Make deferproc just take a func(). throw("defer with non-empty frame") }
// the arguments of fn are in a perilous state. The stack map // for deferproc does not describe them. So we can't let garbage // collection or stack copying trigger until we've copied them out // to somewhere safe. The memmove below does that. // Until the copy completes, we can only call nosplit routines. sp := getcallersp() argp := uintptr(unsafe.Pointer(&fn)) + unsafe.Sizeof(fn) callerpc := getcallerpc()
// 这里分配了 defer 在堆上 d := newdefer(siz) if d._panic != nil { throw("deferproc: d.panic != nil after newdefer") } d.link = gp._defer gp._defer = d d.fn = fn d.pc = callerpc d.sp = sp switch siz { case0: // Do nothing. case sys.PtrSize: *(*uintptr)(deferArgs(d)) = *(*uintptr)(unsafe.Pointer(argp)) default: memmove(deferArgs(d), unsafe.Pointer(argp), uintptr(siz)) }
// deferproc returns 0 normally. // a deferred func that stops a panic // makes the deferproc return 1. // the code the compiler generates always // checks the return value and jumps to the // end of the function if deferproc returns != 0. return0() // No code can go here - the C return register has // been set and must not be clobbered. }