亲宝软件园·资讯

展开

C/C++ 函数原理传参示例详解

amjieker 人气:0

x84-64的寄存器

本文所用gccx86-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

加载全部内容

相关教程
猜你喜欢
用户评论