面试题答案
一键面试栈帧创建
- Go语言函数调用栈帧创建基础:
- 在Go语言函数调用时,栈帧创建主要涉及到为函数局部变量、参数等分配空间。
- 栈是从高地址向低地址增长。
- x86 - 64架构:
- 当一个函数被调用时,
call
指令会将返回地址压入栈中。例如:
call target_function
- 函数开始时,通常会使用
push %rbp
指令将当前栈基指针rbp
压入栈,保存调用者的栈帧信息。然后mov %rsp, %rbp
,将当前栈顶指针rsp
赋值给rbp
,作为新的栈基指针,这样就确定了新函数的栈帧范围。例如:
push %rbp mov %rsp, %rbp sub $N, %rsp ; N为局部变量和其他需要的空间大小,为局部变量分配空间
- 当一个函数被调用时,
- ARM架构:
- 在ARM架构中,函数调用使用
bl
(branch with link)指令来调用函数并保存返回地址到lr
(链接寄存器)。例如:
bl target_function
- 函数开始时,一般通过
stp x29, x30, [sp, #-16]!
指令保存栈基指针x29
(类似x86 - 64的rbp
)和返回地址x30
(类似x86 - 64中保存返回地址的栈位置),并更新栈指针。然后add x29, sp, 0
将栈指针赋值给栈基指针x29
。例如:
stp x29, x30, [sp, #-16]! add x29, sp, 0 sub sp, sp, #M ; M为局部变量和其他需要的空间大小,为局部变量分配空间
- 在ARM架构中,函数调用使用
参数传递
- x86 - 64架构:
- 前6个整数或指针类型参数通过寄存器
rdi
、rsi
、rdx
、rcx
、r8
、r9
传递,超过6个的参数从栈上传递。例如,对于一个有3个参数的函数调用:
mov $param1, %rdi mov $param2, %rsi mov $param3, %rdx call target_function
- 前6个整数或指针类型参数通过寄存器
- ARM架构:
- 前8个通用寄存器
x0
-x7
用于传递参数。例如,对于一个有3个参数的函数调用:
mov x0, $param1 mov x1, $param2 mov x2, $param3 bl target_function
- 前8个通用寄存器
函数返回值处理
- x86 - 64架构:
- 函数返回值通常放在
rax
寄存器中(对于整数和指针类型)。例如,函数执行结束时:
mov result, %rax ; 将返回值放到rax寄存器 leave ; 相当于mov %rbp, %rsp; pop %rbp,恢复调用者栈帧 ret ; 返回,将栈顶的返回地址弹出到指令指针寄存器rip
- 函数返回值通常放在
- ARM架构:
- 函数返回值通常放在
x0
寄存器中(对于整数和指针类型)。例如:
mov x0, result ; 将返回值放到x0寄存器 ldp x29, x30, [sp], #16 ; 恢复栈基指针和返回地址 ret ; 返回,将链接寄存器lr中的值赋给程序计数器pc
- 函数返回值通常放在
不同架构差异总结
- 寄存器使用差异:
- x86 - 64使用
rdi
-r9
等寄存器传递参数,rax
传递返回值;ARM使用x0
-x7
传递参数,x0
传递返回值。 - x86 - 64使用
rbp
作为栈基指针,ARM使用x29
作为栈基指针。
- x86 - 64使用
- 指令集差异:
- x86 - 64使用
call
、ret
等指令进行函数调用和返回;ARM使用bl
、ret
指令。 - 栈操作指令不同,如x86 - 64使用
push
、pop
,ARM使用stp
、ldp
等。
- x86 - 64使用
- 栈帧布局差异:
- x86 - 64和ARM虽然都为局部变量在栈上分配空间,但具体指令和分配方式有所不同,如ARM通过
sub sp, sp, #M
,x86 - 64通过sub $N, %rsp
。
- x86 - 64和ARM虽然都为局部变量在栈上分配空间,但具体指令和分配方式有所不同,如ARM通过