简单函数的返回

        上文http://blog.csdn.net/zr_lang/article/details/39962359我们说到了函数调用,有调用就要有返回,下面我们说函数的返回。首先为了方便先把代码粘贴到这里:

 1#include
 2void tmp_print(int a, int b, int c){
 3   printf ("a=%d, b=%d, c=%d\n", a, b,  c);
 4}
 5
 6int main(int argc, char *argv[]){
 7        int a=0, b=1, c=33;
 8        tmp_print(a, b, c);
 9        return 0;
10}

 这个程序编译成目标文件,然后反汇编看到的代码段为:

Disassembly of section .text:

0000000000000000:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 83 ec 10             sub    $0x10,%rsp
   8:   89 7d fc                mov    %edi,-0x4(%rbp)
   b:   89 75 f8                mov    %esi,-0x8(%rbp)
   e:   89 55 f4                mov    %edx,-0xc(%rbp)
  11:   8b 4d f4                mov    -0xc(%rbp),%ecx
  14:   8b 55 f8                mov    -0x8(%rbp),%edx
  17:   8b 45 fc                mov    -0x4(%rbp),%eax
  1a:   89 c6                   mov    %eax,%esi
  1c:   bf 00 00 00 00          mov    $0x0,%edi
  21:   b8 00 00 00 00          mov    $0x0,%eax
  26:   e8 00 00 00 00          callq  2b 
  2b:   c9                      leaveq 
  2c:   c3                      retq   

000000000000002d:
  2d:   55                      push   %rbp
  2e:   48 89 e5                mov    %rsp,%rbp
  31:   48 83 ec 20             sub    $0x20,%rsp
  35:   89 7d ec                mov    %edi,-0x14(%rbp)
  38:   48 89 75 e0             mov    %rsi,-0x20(%rbp)
  3c:   c7 45 fc 00 00 00 00    movl   $0x0,-0x4(%rbp)
  43:   c7 45 f8 01 00 00 00    movl   $0x1,-0x8(%rbp)
  4a:   c7 45 f4 21 00 00 00    movl   $0x21,-0xc(%rbp)
  51:   8b 55 f4                mov    -0xc(%rbp),%edx
  54:   8b 4d f8                mov    -0x8(%rbp),%ecx
  57:   8b 45 fc                mov    -0x4(%rbp),%eax
  5a:   89 ce                   mov    %ecx,%esi
  5c:   89 c7                   mov    %eax,%edi
  5e:   e8 00 00 00 00          callq  63 
  63:   b8 00 00 00 00          mov    $0x0,%eax
  68:   c9                      leaveq 
  69:   c3                      retq   

 在函数tmp_print被调用后栈的情况是

返回地址      <--  8(%rbp)

旧%rbp        <--  (%rbp)

局部变量1    <--  -4(%rbp)

局部变量2    <--  -8(%rbp)

局部变量3    <--   -12(%rbp)

未使用空间   <--   -16(%rbp) 和 (%rsp) 


为了说明参数通过栈传递的情况,我们假设了参数a,b,c是通过push入栈的(这点和上面的代码不太一样,上面是通过寄存器传递的),那么栈的情况就是:

===栈底方向===

参数 c           <--  32(%rbp)

参数 b          <--  24(%rbp)

参数 a          <--  16(%rbp)

返回地址      <--  8(%rbp)

旧%rbp        <--  (%rbp)

局部变量1    <--  -4(%rbp)

局部变量2    <--  -8(%rbp)

局部变量3    <--   -12(%rbp)

未使用空间   <--   -16(%rbp) 和 (%rsp) 

===栈顶方向===


好了,下面我们继续,以上内容如有不懂请参考上一篇文章。上面描述的是tmp_print所使用的栈的情况,那么tmp_print函数返回后会回到哪呢?这里是返回到main函数,main函数也是函数,注意观察main函数的起始代码,人家也是有栈空间的:

  2d:   55                      push   %rbp
  2e:   48 89 e5                mov    %rsp,%rbp
  31:   48 83 ec 20             sub    $0x20,%rsp
这几行和tmp_print函数的前三行基本一样,main函数在调用tmp_print函数之前栈的情况是这样的:

main的参数(可能有)

main的返回地址

main之前的旧%rbp        <--  (%rbp)

main的局部空间1

main的局部空间2

main的局部空间N          <--  -32(%rbp) 和 (%rsp)


我们将这个状态设为main状态,然后调用tmp_print后就变成:


main的参数

main的返回地址
main之前的旧%rbp
main的局部空间1
main的局部空间2
main的局部空间N
tmp_print的参数 c           <--  32(%rbp)
tmp_print的参数 b          <--  24(%rbp)
tmp_print的参数 a          <--  16(%rbp)
tmp_print返回main的地址      <--  8(%rbp)
main的%rbp                              <--  (%rbp)
tmp_print的局部变量1    <--  -4(%rbp)
tmp_print的局部变量2    <--  -8(%rbp)
tmp_print的局部变量3    <--   -12(%rbp)
tmp_print的局部空间   <--   -16(%rbp) 和 (%rsp) 

我们设这个状态为tmp_print状态,主要的变动就是BP和SP寄存器的变动,当然还有IP寄存器。当tmp_print函数执行完之后就该返回了,那么就要从tmp_print状态回到main状态,怎么做呢?就是调用时候的反操作。

  2b:   c9                      leaveq 
  2c:   c3                      retq   
这两行是tmp_print的返回操作,leaveq相当于:

movq %rbp, %rsp

popq %rbp


movq %rbp, %rsp让%rsp一下就指回了tmp_print的%rbp的位置,也就是现在变成这样:

main的参数
main的返回地址
main之前的旧%rbp
main的局部空间1
main的局部空间2
main的局部空间N
tmp_print的参数 c           <--  32(%rbp)
tmp_print的参数 b          <--  24(%rbp)
tmp_print的参数 a          <--  16(%rbp)
tmp_print返回main的地址      <--  8(%rbp)
main的%rbp                              <--  (%rbp)和(%rsp)

也就是说tmp_print的局部变量空间被放弃了,由于SP是栈指针,它指向的位置就是栈顶,超过它的位置都认为是栈中无意义及未使用的位置。唉,多可怜,tmp_print的局部空间就被SP这轻轻一动废弃了。然后

popq %rbp

这一句相当于动了%rbp和%rsp两个寄存器,注意上面的状态如果再出栈就是让“main的%rbp”值出栈,把这个值赋给%rbp,然后%rsp继续向回移动。也就是现在的状态变为:

main的参数
main的返回地址
main之前的旧%rbp       <--  (%rbp)
main的局部空间1
main的局部空间2
main的局部空间N
tmp_print的参数 c
tmp_print的参数 b
tmp_print的参数 a 
tmp_print返回main的地址      <--  (%rsp)

这个状态就和main状态差不多了,就差rsp没回来了。下面的retq就是让rsp回来,让IP指向函数返回后的下一条指令。retq相当于:

popq %rip

因为上面的状态可以看到,执行这条命令时%rsp指向的就是保存的返回地址,所以这个出栈动作就是将这个返回地址直接给%rip,然后%rsp再向回移动一下,现在状态变为:

main的参数
main的返回地址
main之前的旧%rbp       <--  (%rbp)
main的局部空间1
main的局部空间2
main的局部空间N
tmp_print的参数 c
tmp_print的参数 b
tmp_print的参数 a         <-- (%rsp)

我们前面说了,tmp_print的参数是main函数传递的,如果是通过寄存器传递的这里就已经让%rsp回到main函数的栈顶了,但是如果是通过栈传递的参数,那么返回后还得回收一下这些栈。有人可能问,现在已经返回main函数了,tmp_print怎么回收这些参数空间。所谓杀人偿命欠债还钱,谁干的谁来负责收拾,这也是编程的基本原则之一,如果一个函数分配了一段空间,那么尽量让这个函数自己释放这段空间,以免造成申请空间和释放空间的错乱。通过tmp_print的参数应该是调用者,也就是main函数放进栈中的,那么再返回栈后应由main函数来回收。回收只要执行和申请时的反操作就行了,如果申请时用的是sub SP,那么现在就用add SP,如果申请时用的时push,那么现在就用pop。总之将%rsp移动回main的栈顶,变为这样:

main的参数(可能有)
main的返回地址
main之前的旧%rbp        <--  (%rbp)
main的局部空间1
main的局部空间2
main的局部空间N          <--  -32(%rbp) 和 (%rsp)

到此也就完成了tmp_print函数返回的全部动作,栈空间又变回了调用前的状态。所以说栈空间的东西是很不牢靠的,SP轻轻动几下就废弃了很多东西,你的局部变量啊,参数啊什么的一下子就没意义了,下次再调用别的就给覆盖了。所以认准每一个变量的生存周期很重要。


最后还有一个要说的就是返回值,由于我没给tmp_print函数设置返回值,所以从它就看不出来了。但是可以从main函数看出一下端倪。main函数最后:

  63:   b8 00 00 00 00          mov    $0x0,%eax
  68:   c9                      leaveq 
  69:   c3                      retq   

除了熟悉的,上面说过的leaveq和retq以外还有mov  $0x0,%eax。这个就是将返回值放到AX寄存器中,也就是说AX寄存器是用来保存函数返回值的。


到此一个基本的普通函数调用和返回过程就说完了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值