go打印defer
能解释一下为什么打印结果是这样吗
回答
同为新手,个人理解:
首先defer后进先出,三二一的顺序应该没异议了。
其次根据defer注册要延迟执行的函数时该函数所有的参数都需要确定其值,
。
一二三按照代码顺序逐行注册,其中函数需求的参数也在注册时被赋值。
接着执行a++
随后RET之前开始按序执行defer三二一。
三:func(a int)内的a为形参,在注册时开辟了一块内存存放形参 a‘,其值为传入实参a=1。随后执行输出1
二:fmt.Println(a)同为函数,同上。
一:无参函数,注册时不开辟新内存。执行时先上寻找参数a(此时为2)
这个问题,如果你要想了解它最根本的原理,得去看defer
相关的源码,相关代码在.../runtime/runtime2.go
和.../runtime/panic.go
下。这里贴一下_defer
结构体的源码:
// 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/gc/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
关键字后的函数调用执行会在函数返回前发生;
然后,defer
关键字的执行顺序是倒序的,也就是写在代码最下方的defer
先被调用。这是因为defer
实际是一个链表(上面代码中的link *_defer
,并且运行时会将后出现的defer
追加到链表的最前面,而实际执行时又是从链表头开始执行,所以是倒序。
上面两点应该没什么问题,主要就是你这里a
的赋值问题了。
defer
关键字后面跟的是一个函数调用,你这里的二
和三
其实本质上一样:都是调用了带参数的函数并把a
作为参数传入,只不过三
是一个匿名函数。
注意了,defer
关键字在代码运行到它(不是调用执行它)的时候,会直接拷贝函数参数,也就是当前函数参数的值,而二
和三
又是值传递,不是地址传递。所以,二
和三
在代码执行到这两句时,就已经把当前的a
传进去了,因此输出的是1
。
一
的不同地方在哪里呢?它没有参数。我们知道,如果函数没有参数,当它内部调用一个变量时,就会去作用域外的作用域找,因此它用的是main()
里的a
。那么这个a
为什么会输出2
?就是前面说的:defer
关键字后的函数调用会在函数返回前发生,而那时,defer
的这个匿名函数作用域外的a
已经是2
了。
我们对照代码看一下,代码中,除了前面提到的link
,还有一个fn *funcval
,这货就是defer
后面的函数。
当代码运行到defer
的这一句时,会创建一个新的_defer
结构体,并将defer
后面的函数引用传递进来给fn
,因此这个fn
指向的还是原来那个匿名函数,则它们的作用域自然也相同。
我这里把你问题中的代码稍微改一下,根据上面我说的这些东西,猜猜输出是什么:
package mainimport "fmt"
func main() {
a := 1
defer func() {
fmt.Println("一:", a)
}()
defer fmt.Println("二:", a)
defer func(a *int) {
fmt.Println("四:", *a)
}(&a)
a ++
defer func(a int) {
fmt.Println("三:", a)
}(a)
}
输出结果如下:
三: 2四: 2
二: 1
一: 2
因为 二 和 三 都是传的参数,在执行 defer 那一行的时候,参数已经定了;而 一 是直接使用的 main 函数里面 a
defer 执行的顺序,可以看成是栈,先进后出,后进先出,所以defer执行顺序是:三、二、一
对于变量,就是引用传递和拷贝变量的区别:闭包里边使用外边的变量的时候,应该获取的是变量地址对应的变量值,
那么defer“一”那个闭包里边变量a,此时已经变成了2
defer“二”,在a++之前已经传递了a变量的拷贝,就是a=1
defer“三”,同理,在a++之前已经传递了a变量的拷贝,就是a=1
预期说这是一个关于 defer 的问题,不如说这是一个关于闭包的问题。
以上是 go打印defer 的全部内容, 来源链接: utcz.com/a/41679.html