栈中函数调用原理_详解
来源:互联网 发布:php 文本封装超链接 编辑:程序博客网 时间:2024/05/17 22:31
一、eip、ebp、esp介绍
EIP,EBP,ESP都是系统的寄存器,里面存储的是些地址,我们系统中栈的实现上离不开他们三个。 我知道栈的数据结构主要特点是 后进先处。它还有两个作用: 1.栈是用来存储临时变量,函数传递的中间结果。 2.操作系统维护的,对于程序员是透明的。
下面我们就通过一个小例子说说栈的原理。
先写个小程序:
1 2 3 4 5 6 7 8 9
void fun(void){ printf("helloworld");}void main(void){ fun() printf("函数调用结束");}
来自CODE的代码片snippet_file_0.txt
当程序进行函数调用的时候,我们经常说的是先将函数压栈,当函数调用结束后,再出栈。这一切的工作都是系统帮我们自动完成的。但在完成的过程中,系统会用到下面三种寄存器:EIP、ESP、EBP。
当调用fun函数开始时,三者的作用。
- EIP寄存器里存储的是CPU下次要执行的指令的地址。 也就是调用完fun函数后,让CPU知道应该执行main函数中的printf("函数调用结束")语句了。
- EBP寄存器里存储的是是栈的栈底指针,通常叫栈基址,这个是一开始进行fun()函数调用之前,由ESP传递给EBP的。(在函数调用前你可以这么理解:ESP存储的是栈顶地址,也是栈底地址。)
- ESP寄存器里存储的是在调用函数fun()之后,栈的栈顶。并且始终指向栈顶。
- 系统根据EIP寄存器里存储的地址,CPU就能够知道函数调用完,下一步应该做什么,也就是应该执行main函数中的printf(“函数调用结束”)。
- EBP寄存器存储的是栈底地址,而这个地址是由ESP在函数调用前传递给EBP的。等到调用结束,EBP会把其地址再次传回给ESP。所以ESP又一次指向了函数调用结束后,栈顶的地址。
二、堆和栈
首先要清楚的是程序对内存的使用分为以下几个区:
- 栈区(stack):由编译器自动分配和释放,存放函数的参数值,局部变量的值等。操作方式类似于数据结构中的栈。
- 堆区(heap):一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。与数据结构中的堆是两码事,分配方式类似于链表。
- 全局区(static):全局变量和静态变量存放在此。
- 文字常量区:常量字符串放在此,程序结束后由系统释放。
- 程序代码区:存放函数体的二进制代码。
典型的内存区域分配如图所示:
其次是堆和栈的申请方式:栈由系统自动分配,速度较快,在windows下栈是向低地址扩展的数据结构,是一块连续的内存区域,大小是2MB。堆需要程序员自己申请,并指明大小,速度比较慢。在C中用malloc,C++中用new。另外,堆是向高地址扩展的数据结构,是不连续的内存区域,堆的大小受限于计算机的虚拟内存。因此堆空间获取和使用比较灵活,可用空间较大。
三、栈帧结构和函数调用过程
首先应该明白,栈是从高地址向低地址延伸的。每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(地址地)。下图为典型的存取器安排,观察栈在其中的位置
入栈操作:push eax; 等价于 esp=esp-4,eax->[esp];如下图
出栈操作:pop eax; 等价于 [esp]->eax,esp=esp+4;如下图
我们来看下面这个C程序在执行过程中,栈的变化情况
1 2 3 4 5 6 7 8 9 10 11
void func(int m, int n) { int a, b; a = m; b = n;}main() {... func(m, n);L: 下一条语句...}
来自CODE的代码片snippet_file_0.txt
在main调用func函数前,栈的情况,也就是说main的栈帧:
从低地址esp到高地址ebp的这块区域,就是当前main函数的栈帧。当main中调用func时,写成汇编大致是:
push m
push n; 两个参数压入栈
call func; 调用func,将返回地址填入栈,并跳转到func
当跳转到了func,来看看func的汇编大致的样子:
__func:
push ebp; 这个很重要,因为现在到了一个新的函数,也就是说要有自己的栈帧了,那么,必须把上面的函数main的栈帧底部保存起来,栈顶是不用保存的,因为上一个栈帧的顶部讲会是func的栈帧底部。(两栈帧相邻的)
mov ebp, esp; 上一栈帧的顶部,就是这个栈帧的底部;暂时先看现在的栈的情况
到这里,新的栈帧开始了
sub esp, 8; int a, b 这里声明了两个int,所以esp减小8个字节来为a,b分配空间
mov dword ptr [esp+4],[ebp+12]; a=m
mov dword ptr [esp], [ebp+8];b=n
这样,栈的情况变为:
ret 8 ; 返回,然后8是什么意思呢,就是参数占用的字节数,当返回后,esp-8,释放参数m,n的空间。由此可见,通过ebp,能够很容易定位到上面的参数。当从func函数返回时,首先esp移动到栈帧底部(即释放局部变量),然后把上一个函数的栈帧底部指针弹出到ebp,再弹出返回地址到cs:ip上,esp继续移动划过参数,这样,ebp,esp就回到了调用函数前的状态,即现在恢复了原来的main的栈帧。
- 栈中函数调用原理_详解
- 栈中函数调用原理详解
- 栈帧详解———函数调用原理
- php_函数_函数调用过程及原理理解
- 函数调用栈详解
- 函数调用原理与栈
- c语言中函数调用的原理
- c语言中函数调用的原理
- php调用类中属性和函数的常用方法->_=>_::_$this->
- MYSQL_DATE_FORMAT()_函数_详解
- c++构造函数中调用虚函数的原理剖析
- 函数栈帧(函数调用过程详解)
- 函数调用栈的获取原理分析
- 函数调用栈的获取原理分析
- 函数调用-----压栈的工作原理
- 函数调用 压栈的工作原理
- 函数调用的原理
- C++函数调用原理
- 请定义一个宏,比较两个数a、b的大小,不能使用大于、小于、if语句
- java环境变量配置
- jQuery ajax get请求编码问题,jQuery ajax简化处理,jQuery ajax与Servlet交互
- 【转】图解HTTPS
- 用springMVC做一个多页的用户注册过程
- 栈中函数调用原理_详解
- 【版本升级】MyEclipse 2017 CI 4发布(附下载)
- 使用github作为博客引擎
- JDK的下载与安装、配置
- form表单提交之前校验返回true之后自动提交
- 【转】Python技术博客——老王python
- 获取div的各种高度
- 【转】一条进程的栈区、堆区、数据区和代码区在内存中的映射
- 注册,上传图片,显示