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 main

import "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

回到顶部