C/C++ 函数原理传参示例详解
amjieker 人气:0x84-64的寄存器
本文所用gcc
为 x86-64 gcc 10.1
wiki.cdot.senecacollege.ca/wiki/X86_64…
rax - register a extended
rbx - register b extended
rcx - register c extended
rdx - register d extended
rbp - register base pointer (start of stack)
rsp - register stack pointer (current location in stack, growing downwards)
rsi - register source index (source for data copies)
rdi - register destination index (destination for data copies)
其他寄存器: r8 r9 r10 r11 r12 r13 r14 r15
函数是个什么东西?
一个简单的函数
int func(){} int main() { int x = 2; func(); }
main: pushq %rbp movq %rsp, %rbp subq $16, %rsp movl $2, -4(%rbp) call func() movl $0, %eax leave ret
分配空间动作如下所示:
这里加了个函数调用是因为在有些时候,没有函数调用,就不会使用subq $16, %rsp
这一条指令,我的猜想是既然你都是栈顶的,并且不会再有rbp的变化,那么栈顶以上的元素我都可以随便用。
并且我们观察可以得知,分配栈空间时,他是分配的16个字节,也就是说,有对齐
返回时,弹出栈顶,就可以恢复到上一个栈帧的状态了。
传参姿势
入栈规则
c/c++ 中规定的函数压栈顺序是从右到左,当然,如果你是 Visual C/C++的话,它们有更多的玩法 比如:
template<typename T> T val(T t) { cout << t << endl; return t; } signed main() { printf("%d%d%d", val(1), val(2), val(3)); return 0; }
结果
3
2
1
123
看看汇编
int func(int x, int y, int z) { return 0; } int main() { func(1, 2, 3); }
生成的汇编
func(int, int, int): pushq %rbp movq %rsp, %rbp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl %edx, -12(%rbp) movl $0, %eax popq %rbp ret main: pushq %rbp movq %rsp, %rbp movl $3, %edx movl $2, %esi movl $1, %edi call func(int, int, int) movl $0, %eax popq %rbp ret
上文中可以看出,也证实了我们所观察到的,首先把3传给了edx,2传给了esi,1传给了edi
全都存寄存器吗?
寄存器毕竟少,当然,还可以存在栈上嘛
int fun() {return 0;} int func(int x, int y, int z, int a, int b, int c, int d, int e, int f){ fun(); return e; } int main() { func(1, 2, 3, 4, 5, 6, 7, 8, 9); return 0; }
fun(): pushq %rbp movq %rsp, %rbp movl $0, %eax popq %rbp ret func(int, int, int, int, int, int, int, int, int): pushq %rbp movq %rsp, %rbp subq $24, %rsp movl %edi, -4(%rbp) movl %esi, -8(%rbp) movl %edx, -12(%rbp) movl %ecx, -16(%rbp) movl %r8d, -20(%rbp) movl %r9d, -24(%rbp) call fun() movl 24(%rbp), %eax leave ret main: pushq %rbp movq %rsp, %rbp pushq $9 // 24+%rbp pushq $8 // 16+%rbp pushq $7 // 8 +%rbp movl $6, %r9d movl $5, %r8d movl $4, %ecx movl $3, %edx movl $2, %esi movl $1, %edi call func(int, int, int, int, int, int, int, int, int) addq $24, %rsp movl $0, %eax leave ret
主函数中的这三条语句
pushq $9 pushq $8 pushq $7
说明了,当函数入栈放寄存器放不下时,会放在栈上,放在栈顶之上,等函数调用执行完成后,rbp取出回到当前位置之后,再去addq $24, %rsp
把栈弹出这些元素。
并且func函数中的movl 24(%rbp), %eax
也证明了,传的参数是在栈顶的上面(自上向下增长) 24 + %rbp
刚好是 $9
, 也就是局部变量f的位置
传对象呢?
在这里,暂且不谈内存布局,把一个对象看成一块内存对于的位置
这里用一个结构体做示例
struct E {int x, y, z;}; E func(E e){ e.x = 2; return e; } int main() { E e = {.x = 1, .y = 2, .z = 3}; e = func(e); return 0; }
func(E): pushq %rbp movq %rsp, %rbp // 将rdi 和 esi 取出来 放到 rdx 和 eax 中 movq %rdi, %rdx movl %esi, %eax // 存放到开辟好的空间中 {x = rbp - 32, y = rbp - 28, z = rbp - 24} movq %rdx, -32(%rbp) movl %eax, -24(%rbp) // 更改 x movl $2, -32(%rbp) // 将值移动到寄存器上,从返回寄存器上移动到局部返回出去的变量 movq -32(%rbp), %rax movq %rax, -12(%rbp) movl -24(%rbp), %eax movl %eax, -4(%rbp) // 将返回值值移动到寄存器上 rax rdx 上 movq -12(%rbp), %rax movl -4(%rbp), %ecx movq %rcx, %rdx popq %rbp ret main: // 压栈保存现场 没什么好说的 pushq %rbp movq %rsp, %rbp subq $16, %rsp // 内存布局 rbp | z rbp - 4 | y rbp - 8 | x rbp - 12 movl $1, -12(%rbp) movl $2, -8(%rbp) movl $3, -4(%rbp) // 移动 x 和 y 到 rdx 寄存器中 movq -12(%rbp), %rdx // 移动 z 到 eax中 movl -4(%rbp), %eax // 再将 rdx 和 eax 分别移动到rdi 和 esi中 movq %rdx, %rdi movl %eax, %esi call func(E) // 从rax 中取出x y movq %rax, -12(%rbp) // 从rdx中取出z movl -4(%rbp), %eax andl $0, %eax orl %edx, %eax // movl %eax, -4(%rbp) movl $0, %eax leave ret
加载全部内容