调用惯例
对于一个简单的函数:
package main
func myFunction(a, b int) (int, int) {
return a + b, a - b
}
func main() {
myFunction(66, 77)
}
myFunction函数接收两个整数并返回两个整数。
通过go tool compile 获取汇编指令go tool compile -S -N -l main.go
"".main STEXT size=68 args=0x0 locals=0x28
0x0000 00000 (main.go:7) MOVQ (TLS), CX
0x0009 00009 (main.go:7) CMPQ SP, 16(CX)
0x000d 00013 (main.go:7) JLS 61
0x000f 00015 (main.go:7) SUBQ $40, SP // 分配 40 字节栈空间
0x0013 00019 (main.go:7) MOVQ BP, 32(SP) // 将基址指针存储到栈上
0x0018 00024 (main.go:7) LEAQ 32(SP), BP
0x001d 00029 (main.go:8) MOVQ $66, (SP) // 第一个参数
0x0025 00037 (main.go:8) MOVQ $77, 8(SP) // 第二个参数
0x002e 00046 (main.go:8) CALL "".myFunction(SB)
0x0033 00051 (main.go:9) MOVQ 32(SP), BP
0x0038 00056 (main.go:9) ADDQ $40, SP
0x003c 00060 (main.go:9) RET
根据main函数生成的汇编指令,可以分析出main函数调用myFunction之前的栈
main 函数通过 SUBQ $40, SP
指令一共在栈上分配了 40 字节的内存空间:
空间 | 大小 | 作用 |
---|---|---|
SP+32 ~ BP | 8 字节 | main 函数的栈基址指针 |
SP+16 ~ SP+32 | 16 字节 | 函数 myFunction 的两个返回值 |
SP ~ SP+16 | 16 字节 | 函数 myFunction 的两个参数 |
表 4-1 主函数调用栈
myFunction 入参的压栈顺序和 C 语言一样,都是从右到左,即第一个参数 66 在栈顶的 SP ~ SP+8,第二个参数存储在 SP+8 ~ SP+16 的空间中。
小结:
函数的入参和返回值,从右到左被压入栈中,即最左边的入参在栈顶,最右边的返回值在栈底(base pointer 之上)
随后调用myFunction :CALL "".myFunction(SB)
这个指令会将main函数的返回地址压入栈顶,随后调用fyFunction汇编指令
"".myFunction STEXT nosplit size=49 args=0x20 locals=0x0
0x0000 00000 (main.go:3) MOVQ $0, "".~r2+24(SP) // 初始化第一个返回值
0x0009 00009 (main.go:3) MOVQ $0, "".~r3+32(SP) // 初始化第二个返回值
0x0012 00018 (main.go:4) MOVQ "".a+8(SP), AX // AX = 66
0x0017 00023 (main.go:4) ADDQ "".b+16(SP), AX // AX = AX + 77 = 143
0x001c 00028 (main.go:4) MOVQ AX, "".~r2+24(SP) // (24)SP = AX = 143
0x0021 00033 (main.go:4) MOVQ "".a+8(SP), AX // AX = 66
0x0026 00038 (main.go:4) SUBQ "".b+16(SP), AX // AX = AX - 77 = -11
0x002b 00043 (main.go:4) MOVQ AX, "".~r3+32(SP) // (32)SP = AX = -11
0x0030 00048 (main.go:4) RET
myFunction首先对返回值进行初始化,再进行计算并存入栈中
随后main函数恢复栈基指针并销毁失去作用的40字节栈内存
小结:
Go语言使用栈 传递参数 和 接收返回值 ,所以只需要在栈上多分配一些内存就可以返回多个值
参数传递
值传递和引用传递:
- 传值:函数调用时会对参数进行拷贝,被调用方和调用方两者持有不相关的两份数据;
- 传引用:函数调用时会传递参数的指针,被调用方和调用方两者持有相同的数据,任意一方做出的修改都会影响另一方
Go语言选用了传值的方式,无论是传递基本类型、结构体还是指针,都会对传递的参数进行拷贝
将指针作为参数传入某个函数时,函数内部会复制指针,也就是会同时出现两个指针指向原有的内存空间,所以 Go 语言中传指针也是传值。