函数调用栈
func A(){
...
...
}
func B(){
...
...
}
我们按照编程语言的语法定义的函数,会被编译成一堆机器指令,写入可执行文件,程序执行时,可执行文件加载到内存,这些机器指令对应到虚拟地址空间中,位于代码段,如下图
如果在一个函数中调用另一个函数,编译器就会对应生成一条call指令,程序执行到这条指令的时候,就会跳转到被调用函数入口处开始执行,每个函数的最后都有一条ret指令,负责在函数结束后跳回到调用处,继续执行。
函数栈帧
函数执行时需要有足够的内存空间,供它存放局部变量,参数等数据,这段空间对应到虚拟地址空间的栈,分配给函数的空间称为函数栈帧
call指令只做两件事:
- 第一件将下一条指令的地址入栈,就是返回地址,被调用函数执行结束后会跳转回这里.
- 第二件跳转到被调用的函数入口处执行
函数传参
值传递
传参是值拷贝,所以拷贝整形变量a,b的值,由图可以知道a,b值交换失败的原因了,交换的并不是局部变量的a,b
注意:参数入栈顺序是由右向左
指针传递
交换成功,交换的是两个指针指向的数据。
函数返回值
案例1:(匿名返回值)
func incr(a int)int{
var b int
defer func(){
a++
b++
}
a++
b=a
return b
func main(){
var a,b int
b=incr(a)
fmt.Println(a,b)//0,1
}
程序未执行时在栈中地址分布如图
由函数可知,incr
函数中a先自增1,栈中args
变成1,执行到b = a
时,被调用函数BP of incr
中b = 1
,到return
时,先会把局部变量b的值拷贝到返回值空间,return value
中0变成1,然后在执行注册的defer函数再return,因为a++
所以args
中变成2,执行b++
时,被调用函数BP of incr
中b = 2
incr函数结束,栈中结果如下:
案例2:(命名返回值)
func incr(a int)(b int){
defer func(){
a++
b++//返回值b自增一
}()
a++
return a
}
func main(){
var a,b int
b = incr(a)
fmt.Println(a,b)//0,2
}
最终执行情况如下: