为什么函数是语言的一个核心元素
由基于堆栈的程序执行模型决定的
分析函数底层实现的两种方式
语言编译器源码
反汇编
需要有一定的汇编基础
本章会用到的一些汇编指令
Go语言编译器特点:
-
产生的汇编代码是中间抽象态,不是对机器码的映射
-
有些寄存器真实存在,有些是抽象的寄存器
Go中一些抽象的寄存器:
-
SB(Static base pointer):静态基址寄存器。和全局符号一起表示全局变量的地址
-
FR(Frame pointer):栈帧寄存器。 指向当前函数调用栈帧的栈底位置
-
PC(Program counter):程序计数器。 存放下一条指令的执行地址 --->一般是
CALL、RET
等指令隐式操作 -
SP(Stack pointer):栈顶寄存器。
-
在函数调用前由主调函数设置SP
-
负责对栈空间进行分配或回收
-
特点:
Go 汇编编译器对内嵌汇编程序自动做了调整,增加了保护现场,以及函数调用前的保持 PC 、SP 偏移地址重定位等逻辑,反汇编代码更能反映程序的真实执行逻辑
函数调用规约
采取模式:
-
caller-save模式:由调用者负责保护寄存器
-
在主调函数调用被调函数的前后有一个保存现场和恢复现场的动作
Go中多返回值实现的分析
交换函数swap:
package main
import "fmt"
/*
手写一个交换函数,实现传入两个数,交换他们的值
*/
func swap(arr int, brr int) (int, int) { //函数调用前己经为返回值和参数分配了栈空间,分配顺序是从右向左的,先是返回值,然后是参数
/*交换值*/
arr, brr = brr, arr
return arr, brr
}
func main() {
fmt.Println(swap(10, 20))
}
省略掉汇编代码,只看结果可知:
-
函数的调用者负责环境准备,包括为参数和返回值开辟栈空间。
-
寄存器的保存和恢复也由调用方负责。
-
函数调用后回收栈空间,恢复 BP 也由主调函数负责。
函数多指返回的实质:
-
在栈上开辟多个地址分别存放返回值
-
如果返回值是存放到堆上,则多了一个复制的动作
main
调用swap
的栈的结构图:
函数多值返回的实现:
-
主调函数预先分配好空间来存放返回值,被调函数执行时将返回值复制到该返回位置
-
分配空间的特点:
-
Go语言闭包实现的分析
闭包的结构:
-
函数指针
-
对外部环境的引用
Go闭包的示例代码:
package main
/*
实现一个闭包函数,打印形参的值
*/
func close(arr int) func() {
return func() {
print(arr)
}
}
func main() {
//设置函数变量
f := close(100)
//调用函数
f()
}
在汇编中是通过返回一个结构体来实现闭包的功能:
type Closure struct {
F uintptr
env *Type
}
代码分析:
-
F 是返回的匿名函数指针
-
env 是对外部环境变量的引用集合
-
如果闭包内没有修改外部变量,则 Go 编译器直接优化为值传递。否则是通过指针传递
-
章节小结:
-
函数的底层实现在汇编层面的应用
-
多值返回在内存层面的实现
-