chenpengcong / blog

14 stars 3 forks source link

函数返回值传递 #17

Open chenpengcong opened 6 years ago

chenpengcong commented 6 years ago

我们知道一般情况下函数的返回值是通过eax或rax寄存器来返回的,但是当我们返回值超过eax和rax所能存储的大小情况呢?

以下面代码为例,函数返回大小为32字节的结构体对象

本机环境:Linux cike-pc 4.14.0-deepin2-amd64 #1 SMP PREEMPT Deepin 4.14.12-2 (2018-01-06) x86_64 GNU/Linux

struct Test
{
    char buf[32];
};
struct Test foo()
{
    struct Test t2; 
    return t2;
}
int main()
{
    struct Test t1 = foo();
    return 0;
}

查看该文件编译后的汇编代码

$ objdump -d test.o 

test.o:     file format elf64-x86-64

Disassembly of section .text:

0000000000000000 <foo>:
   0:    55                       push   %rbp
   1:    48 89 e5                 mov    %rsp,%rbp
   4:    48 89 7d d8              mov    %rdi,-0x28(%rbp)
   8:    48 8b 45 d8              mov    -0x28(%rbp),%rax
   c:    48 8b 55 e0              mov    -0x20(%rbp),%rdx
  10:    48 89 10                 mov    %rdx,(%rax)
  13:    48 8b 55 e8              mov    -0x18(%rbp),%rdx
  17:    48 89 50 08              mov    %rdx,0x8(%rax)
  1b:    48 8b 55 f0              mov    -0x10(%rbp),%rdx
  1f:    48 89 50 10              mov    %rdx,0x10(%rax)
  23:    48 8b 55 f8              mov    -0x8(%rbp),%rdx
  27:    48 89 50 18              mov    %rdx,0x18(%rax)
  2b:    48 8b 45 d8              mov    -0x28(%rbp),%rax
  2f:    5d                       pop    %rbp
  30:    c3                       retq   

0000000000000031 <main>:
  31:    55                       push   %rbp
  32:    48 89 e5                 mov    %rsp,%rbp
  35:    48 83 ec 20              sub    $0x20,%rsp
  39:    48 8d 45 e0              lea    -0x20(%rbp),%rax
  3d:    48 89 c7                 mov    %rax,%rdi
  40:    b8 00 00 00 00           mov    $0x0,%eax
  45:    e8 00 00 00 00           callq  4a <main+0x19>
  4a:    b8 00 00 00 00           mov    $0x0,%eax
  4f:    c9                       leaveq 
  50:    c3                       retq

汇编代码的意义如下

首先看main函数,函数main在栈上开辟0x20大小的空间,并将栈顶的地址赋值给rdi寄存器(作为传递给foo的第一个参数)。

  35:    48 83 ec 20              sub    $0x20,%rsp
  39:    48 8d 45 e0              lea    -0x20(%rbp),%rax
  3d:    48 89 c7                 mov    %rax,%rdi

接下来看foo函数,函数foo在栈上开辟0x28大小的空间,并将栈顶(8个字节)的值赋值为%rdi(即main函数传递进来的地址)。然后将该栈上剩下的0x20大小的内存数据对应复制到main函数开辟的0x20大小的栈空间。

   4:    48 89 7d d8              mov    %rdi,-0x28(%rbp)
   8:    48 8b 45 d8              mov    -0x28(%rbp),%rax
   c:    48 8b 55 e0              mov    -0x20(%rbp),%rdx
  10:    48 89 10                 mov    %rdx,(%rax)
  13:    48 8b 55 e8              mov    -0x18(%rbp),%rdx
  17:    48 89 50 08              mov    %rdx,0x8(%rax)
  1b:    48 8b 55 f0              mov    -0x10(%rbp),%rdx
  1f:    48 89 50 10              mov    %rdx,0x10(%rax)
  23:    48 8b 55 f8              mov    -0x8(%rbp),%rdx
  27:    48 89 50 18              mov    %rdx,0x18(%rax)

可以这样理解上述的执行过程:

  1. main函数新建一个struct Test类型变量t1,并将t1的地址作为隐藏参数传递给foo函数
  2. foo函数新建一个struct Test类型变量t2,并将t2的内容拷贝给t1

由于t2的内容已经拷贝到t1了,所以t1就相当于foo函数返回的t2

从上面分析来看,其实foo函数的声明struct Test foo()更像是void foo(struct Test *ret)