函数会被编译器编译为一堆机器指令,写入可执行文件,程序执行时。可执行文件被加载到内存。这些机器指令对应到虚拟地址空间(堆、栈、数据段、代码段)中。 函数执行时需要的局部变量、参数、返回值对应到虚拟地址空间的栈。

image.png

  1. 通过堆栈传递参数,入栈的顺序是从右到左;
  2. 函数返回值通过堆栈传递并由调用者预先分配内存空间;
  3. 调用函数时都是传值,接收方会对入参进行复制再计算;

局部变量、参数、返回值等这段空间对应到虚 拟地址空间的栈

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结构体包装这个地址,然后使用一个二级指针来调用呢,这里主要是为了处理闭包的情况。
image.png
到执行阶段才会创建对应的闭包对象

Go 函数可以是一个闭包。闭包是一个函数值,它引用了其函数体之外的变量。该函数可以访问并赋予其引用的变量的值,换句话说,该函数被这些变量“绑定”在一起。
例如,函数 adder 返回一个闭包。每个闭包都被绑定在其各自的 sum 变量上。

闭包(Closure)是匿名函数的一个特例。当一个匿名函数所访问的变量定义在函数体的外部时,就称这样的匿名函数为闭包。

  1. package main
  2. import "fmt"
  3. func adder() func(int) int {
  4. sum := 0
  5. return func(x int) int {
  6. sum += x
  7. return sum
  8. }
  9. }
  10. func incr() func() int {
  11. var a int = 1
  12. return func() int {
  13. a++
  14. return a
  15. }
  16. }
  17. func main() {
  18. pos, neg := adder(), adder()
  19. for i := 0; i < 10; i++ {
  20. fmt.Println(
  21. pos(i),
  22. neg(-2*i),
  23. )
  24. }
  25. m := inc()
  26. fmt.Println(m()) // 2
  27. fmt.Println(m()) // 3
  28. fmt.Println(m()) // 4
  29. }

i := incr():通过把这个函数变量赋值给 i,i 就成为了一个闭包。
所以 i 保存着对 x 的引用,可以想象 i 中有着一个指针指向 x 或 i 中有 x 的地址。
由于 i 有着指向 x 的指针,所以可以修改 x,且保持着状态:

也就是说,x 逃逸了,它的生命周期没有随着它的作用域结束而结束。
但是这段代码却不会递增:

  1. println(incr()()) // 2
  2. println(incr()()) // 2
  3. println(incr()()) // 2

这是因为这里调用了三次 incr(),返回了三个闭包,这三个闭包引用着三个不同的 x,它们的状态是各自独立的。

闭包引用

  1. x := 1
  2. f := func() {
  3. println(x)
  4. }
  5. x = 2
  6. x = 3
  7. f() // 3

注意
**
如果把x = 2,x = 3 和f()交换顺序,则会输出1 因为f调用时就已经解引用取值了,这之后的修改就与它无关了。

可以通过在闭包内外打印所引用变量的地址来证明:

  1. x := 1
  2. func() {
  3. println(&x) // 0xc0000de790
  4. }()
  5. println(&x) // 0xc0000de790

引用的是同一个地址

参考