实验环境
- Ubuntu 20.10 x86_64 5.4.0-48
- Go 1.17.12
进程虚拟地址空间
栈帧分配是在运行时动态申请和释放的, 如图所示,是进城虚拟地址空间组成的一部分,Linux 进程默认栈空间大小8MB, 那么栈空间如何申请和释放的呢?请往下看…
ubuntu@localhost:~$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 14635
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 14635
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
栈空间申请和释放
栈空间的申请和释放主要和两个寄存器有关:
- BP (Base Pointer) 栈基
- SP (Stack Pointer) 栈指针
栈申请
SUBQ $48, SP //SUBQ 减法 栈申请48字节,因为栈是从高地址--> 低地址
MOVQ BP, 40(SP) //存储 BP值
LEAQ 40(SP), BP //更新 BP值
栈释放
MOVQ 40(SP), BP //还原 BP值
ADDQ $48, SP //ADDQ 加法 栈释放48字节,因为栈是从高地址--> 低地址
RET //返回执行下一条指令
函数栈空间
- caller 函数调用方 (A)
- callee 函数被调用方 (B)
GoLang 语言 caller 负责 callee 的参数和返回值空间的申请和释放
示例验证
package main
//go:noinline
func sum(a, b int) int {
sum := 0
sum = a + b
return sum
}
//go:noinline
func main() {
a := 3
b := 5
c := sum(a, b)
a = c + b
}
go tool compile -N -l -S cmd.go | less //查看汇编代码
"".sum STEXT nosplit size=70 args=0x10 locals=0x18 funcid=0x0
.....
MOVQ AX, "".a+32(SP) 访问到"".main的栈空间, 即 "".a+32(SP) =="".main+0(SP)
MOVQ BX, "".b+40(SP) 访问到"".main的栈空间, 即 "".b+40(SP) =="".main+8(SP)
...
"".main STEXT size=88 args=0x0 locals=0x30 funcid=0x0
...
MOVQ AX, "".c+16(SP) 访问到
...
验证:GoLang 语言 caller 负责 callee 的参数和返回值空间的申请和释放
遗留问题
"".sum STEXT nosplit size=70 args=0x10 locals=0x18 funcid=0x0
0x0000 00000 (cmd.go:4) TEXT "".sum(SB), NOSPLIT|ABIInternal, $24-16
...
$24-16: $24 表示栈大小,16网友描述说是参数+返回值大小,那么应该是18,但实际是16, 感觉只是参数的大小。如果你知道原因,请在评论区留言,感谢。