函数会被编译器编译为一堆机器指令,写入可执行文件,程序执行时。可执行文件被加载到内存。这些机器指令对应到虚拟地址空间(堆、栈、数据段、代码段)中。 函数执行时需要的局部变量、参数、返回值对应到虚拟地址空间的栈。
- 通过堆栈传递参数,入栈的顺序是从右到左;
- 函数返回值通过堆栈传递并由调用者预先分配内存空间;
- 调用函数时都是传值,接收方会对入参进行复制再计算;
局部变量、参数、返回值等这段空间对应到虚 拟地址空间的栈
Go语言中函数栈帧布局是这样的,先是调用者栈基地址,接下来是局部变量,然后是调用函数的返回值,最后是参数,call指针只做两件事,第一,将下一条指令的地址入栈,这就是返回值。被调用函数执行结束后会跳转到这里;第二,调转到被调用的函数入口处执行。后面就是被调用函数的栈帧了(stack frame fo call).所有的函数栈帧布局都遵循统一的约定。所以被调用者是通过栈指针加上相应的偏移 来定位到每个参数和返回值的
init函数
- init函数先于main函数自动执行,不能被其他函数调用;
- init函数没有输入参数、返回值;
- 每个包可以有多个init函数,首先按照源文件名的字典顺序从前往后依次执行,若一个文件中出现多个init(),按照先后顺序从前往后执行
- 包的每个源文件也可以有多个init函数,这点比较特殊;
- 如果当前包包含多高依赖包,则先初始化依赖包
- 只希望执行某个包中的init函数,import包即可,下划线忽略
闭包
函数是头等对象、可以作为参数传递,可以做函数返回值,也可以绑定到变量,go语言称这样的参数。返回值或变量为function value,函数的指令在编译期间生成,而function value本质上是一个指针,但是并不直接指向函数入口,而是指向一个runtine.funcval结构体,这个结构体里面只有一个地址,就是这个函数指令的入口地址。若函数a被赋值给f1和f2两个变量,这种情况编译器会作出优化,让f1和f2共用一个funcval结构体。编译阶段会在只读数据段分配一个funcval结构体,fn指向函数入口,而他本身的地址会在执行阶段赋值给f1和f2,通过f1来执行函数。就会通过它存储的地址,找到对应的funcval结构体。拿到函数入口地址,然后跳转执行。既然只要有函数入口地址就能调用,为什么要通过funcval结构体包装这个地址,然后使用一个二级指针来调用呢,这里主要是为了处理闭包的情况。
到执行阶段才会创建对应的闭包对象
Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数 adder
返回一个闭包。每个闭包都被绑定在其各自的 sum
变量上。
闭包(Closure)是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数体的外部时,就称这样的匿名函数为闭包。
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func incr() func() int {
var a int = 1
return func() int {
a++
return a
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
m := inc()
fmt.Println(m()) // 2
fmt.Println(m()) // 3
fmt.Println(m()) // 4
}
i := incr():通过把这个函数变量赋值给 i,i 就成为了一个闭包。
所以 i 保存着对 x 的引用,可以想象 i 中有着一个指针指向 x 或 i 中有 x 的地址。
由于 i 有着指向 x 的指针,所以可以修改 x,且保持着状态:
也就是说,x
逃逸了,它的生命周期没有随着它的作用域结束而结束。
但是这段代码却不会递增:
println(incr()()) // 2
println(incr()()) // 2
println(incr()()) // 2
这是因为这里调用了三次 incr()
,返回了三个闭包,这三个闭包引用着三个不同的 x
,它们的状态是各自独立的。
闭包引用
x := 1
f := func() {
println(x)
}
x = 2
x = 3
f() // 3
注意
**
如果把x = 2,x = 3 和f()交换顺序,则会输出1 因为f调用时就已经解引用取值了,这之后的修改就与它无关了。
可以通过在闭包内外打印所引用变量的地址来证明:
x := 1
func() {
println(&x) // 0xc0000de790
}()
println(&x) // 0xc0000de790
引用的是同一个地址
参考