x84-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
以上就是C/C++ 函数原理传参示例详解的详细内容,更多关于C/C++ 函数原理传参的资料请关注编程网其它相关文章!