C语言中的函数调用,栈的使用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011433762/article/details/49761251

本文共包含一下四个部分。

  • C源代码
  • 注释
  • 对应汇编代码:此汇编使用”gcc -S hello.c”命令编译生成,部分删减
  • 栈空间的使用过程:包括5个部分,五张图

  • C源码
int sayhello(int a,int b,int c){
    int aa=100;
    int bb=200;
    bb=a;
    bb=b;
    bb=c;
    return aa;
}
main()
{
    int a=100;
    int b=10;
    int c=102;
    c=sayhello(a,b,c);
    c=200;
}
  • 汇编代码

080483ec <sayhello>:
 80483ec:   push   %ebp         //把main函数的ebp放入栈顶
 80483ed:   mov    %esp,%ebp    //ebp指向新的栈帧
 80483ef:   sub     $0x10,%esp //为esp分配了16个字节空间
 80483f2:   movl   $0x64,-0x8(%ebp)    //栈内变量aa=200
 80483f9:   movl   $0xc8,-0x4(%ebp)    //栈内变量bb=200
 8048400:   mov    0x8(%ebp),%eax   //ebp向上取出a=100
 8048403:   mov    %eax,-0x4(%ebp)  //赋值给bb
 8048406:   mov    0xc(%ebp),%eax   //ebp向上取出b=10
 8048409:   mov    %eax,-0x4(%ebp)  //赋值给bb
 804840c:   mov    0x10(%ebp),%eax  //ebp向上取值c=102
 804840f:   mov    %eax,-0x4(%ebp)  //赋值给bb
 8048412:   mov    -0x8(%ebp),%eax  //取出aa的值,存入eax
 8048415:   leave  
 8048416:   ret         //返回现场

08048417 <main>:
 804841a:   sub     $0x1c,%esp     //为main函数的栈分配28字节空间
 804841d:   movl   $0x64,-0xc(%ebp)    //变量a=100
 8048424:   movl   $0xa,-0x8(%ebp) //变量b=10
 804842b:   movl   $0x66,-0x4(%ebp)    //变量c=102
 8048432:   mov    -0x4(%ebp),%eax  //把参数c放入栈顶
 8048435:   mov    %eax,0x8(%esp)
 8048439:   mov    -0x8(%ebp),%eax  //把参数b放入栈顶
 804843c:   mov    %eax,0x4(%esp)   
 8048440:   mov    -0xc(%ebp),%eax  //把参数a放入栈顶
 8048443:   mov    %eax,(%esp)  
 8048446:   call     80483ec <sayhello>//调用sayhello,隐含把EIP放入栈顶
 804844b:   mov    %eax,-0x4(%ebp)     //返回值赋给c
 804844e:   movl   $0xc8,-0x4(%ebp)    //c重新赋值
 8048455:   leave  
 8048456:   ret    
  • 栈调用过程

1.

main函数汇编代码头部分我删去了几行,先不要理会,我们需要的是一个确定的干净的简单的起点,那就从三个变量的定义和初始化开始吧,从代码中可以看出,三个变量的定义和创建使用了ebp的相对位置并且是逆序存放;

此时esp指向的位置比较有意思,从ebp到esp这段空间就是gcc为main函数分配的栈帧,也就是说main函数的运行以后只会使用这一段空间了;这一段空间的大小由两部分组成,如图解释。main函数中定义了三个局部变量12个字节,gcc为局部变量分配了16个字节;main函数调用的函数最多入参个数是3,gcc再分配12个字节,所以main函数的栈帧总共有28个字节,7个字。

2.

这里写图片描述
main函数准备调用sayhello了!
1、先参考esp的位置把入参mov栈中,没有使用PUSH,没有使用PUSH,没有使用PUSH
2、调用call指令,call指令其实隐含了一个PUSH指令,一个MOV指令。他先把当前main的EIP压入栈中,然后把sayhello的代码位置MOV给EIP。程序下一步执行就到了sayhello位置了。注意,保护现场工作尚未完成!

3.

这里写图片描述
进入sayhello函数后继续进行现场保护工作,保存main函数的ebp。之后ebp可以指向为sayhello分配的栈帧了,再为sayhello分配局部变量空间16个字节,创建两个变量。接下来就要使用入参了,入参的访问使用了当前ebp的相对寻址,不过不是向sayhello的栈帧,而是向上取main函数栈帧底部的三个参数!ok,更新完毕,准备返回。

4.

这里写图片描述
准备返回:1、返回的参数放入eax寄存器。2、执行ret。ret负责恢复现场,和call一样隐含了好几个POP和MOV。先让esp指向当前栈帧的底部ebp,然后pop出ebp回复main的栈帧,pop出EIP回复main的代码指针,此时虽然esp上面还存着sayhello的入参,但已经没用了。

5.

这里写图片描述
函数继续运行,EIP已经指向main的代码了,main开始对c进行赋值,先把eax里的数据拿出来,然后赋值给c。结束。

注释

  • esp:当前栈的栈顶指针
  • ebp:当前函数的栈帧基地址
  • ess:栈空间的基地址
  • eax:临时变量寄存器
  • EIP:当前函数的代码指针

栈有很多栈帧组成,随着程序的运行,每进入新的函数都需要创建一个栈帧,函数只使用自己的栈帧,函数运行结束够要弹出栈帧。
刚进入函数时,esp会先跳一段空间,用来存储局部变量。
可以看出,栈只有在保护现场时使用push,pop,其他都是采用寻址的方式使用的。

猜你喜欢

转载自blog.csdn.net/u011433762/article/details/49761251
今日推荐