一、堆栈基础——内存区域
代码是以进程的形式来执行的,一个进程可能被分配到不同的内存区域去执行:
- 代码区:这个区域内存储着被装入执行的二进制机器代码,处理器会到这个区域取指并执行
- 数据区:用于存储全局变量/静态变量等
- 堆区:进程可以在堆区动态的请求一定大小的内存,并在使用完之后还给堆区。动态分配和回收是堆区的特点,是向高地址扩展的内存区域,通常由程序员来管理
-
栈区:用于动态的存储函数之间的调用关系,以保证调用函数在返回时恢复到母函数中继续执行,是由操作系统来管理的。
栈区
栈是向低地址扩展的数据结构,入栈时是从高地址向低地址扩展,是一块连续的内存的区域。通常用来存储局部变量。栈顶的地址和栈的最大容量是系统预先规定好的,在windows下,栈的默认大小是2M,如果申请的空间超过栈的剩余空间,将提示overflow
堆区
堆是向高地址扩展的数据结构,是不连续的内存区域,堆的大小受限于计算机的虚拟内存。
操作系统中有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表
- 寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序
- 由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动将多余的那部分重新放入空闲链表中。
-
对于大多数系统,会在这块内存空间中的首地址记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间
区别
申请方式
栈:由系统自动分配。
堆:需要程序员自己申请,并指明大小,在c中malloc函数 例如p1=(char *)malloc(10)
申请效率
栈:系统自动分配,速度比较快,但是程序员无法控制
堆:由程序员分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来方便
二、堆栈基础——函数调用
函数调用时,将借助系统栈来完成函数状态的保存和恢复
主函数中调用函数A,函数A又调用的函数B,函数B的返回值加上一个值作为函数A的返回值,最后函数A的返回结果作为主函数的返回值
- 这些代码区中精确的跳转都是在与系统栈巧妙的配合过程中完成的。
- 当函数被调用时,系统栈会为这个函数开辟一个新的栈帧,并把它压入栈中。每个栈帧对应着一个未运行完的函数。栈帧中保存了该函数的返回地址和局部变量。从逻辑上讲,栈帧就是一个函数执行的环境:函数参数、函数的局部变量、函数执行完后返回到哪里等待。
-
当函数返回时,系统栈会自动弹出该函数所对应的的栈帧,弹出栈帧意味着这个函数执行完毕。
函数调用的步骤
栈是先进后出的数据结构
- (1)参数入栈——将参数从右向左依次压入系统栈中
- (2)返回地址入栈——将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行
- (3)代码区跳转——处理器从当前代码区跳转到被调用函数的入口(就是跳转到被调用的函数处,再取指令时就从被调用的函数的代码区开始取指令)
- (4)栈帧调整。取指令时,会包括栈帧调整。具体包括
保存当前栈帧状态值,已备后面恢复本栈帧时使用。
将当前栈帧切换到新栈帧。
三、堆栈基础——常见寄存器和栈帧
寄存器
寄存器是中央处理器CPU的组成部分。寄存器是有限存储容量的高速存储部件,它们可以用来暂存指令、数据和地址。我们常常看到32位CPU、64位CPU这样的名称,其实指的就是寄存器的大小。32位CPU寄存器大小就是4字节。CPU越大说明CPU一次处理的数据就越多
为了缓解CPU和内存效率不对等的问题,CPU自带一级缓存和二级缓存,但是这些缓存还是不够快,于是将一些最常访问的数据存放在寄存器中(寄存器的读写速度比内存快很多),CPU优先读取寄存器,再由寄存器跟内存交换数据。
CPU、寄存器和内存
CPU运算所需数据只能从寄存器存取,寄存器会先从高速缓存区读取数据,不命中才会从内存读数据。而内存中的数据也是先从磁盘缓存区(disk cache)读数据,或者在磁盘缓冲区(disk buffer)存数据,最后才会交际最慢的磁盘。
ESP和EBP两个寄存器
栈指针寄存器ESP(extended 寄存器 stack栈 pointer指针)
ESP标识了当前栈帧的顶部(相当于top)
基址指针寄存器ESP(extended 寄存器 base基址 pointer指针)
ESP标识了当前栈的底部(相当于base)
ESP和EBP之间的内存空间为当前帧,当前帧指的是系统栈中最顶部的栈帧。**
函数栈帧中包含的信息
- 局部变量
- 栈帧状态值,用于在本帧被弹出后恢复出上一个栈帧
-
函数返回地址,以便函数返回时能够恢复到函数被调用前的代码区中继续执行指令
指针寄存器EIP(extended 寄存器 instruction pointer指针)
指针寄存器,期内存放着一个指针,该指针永远指向下一条等待执行的指令地址。
函数调用完毕后,返回原来函数指令运行的一个关键操作是,将栈帧中保持的返回地址装入EIP寄存器。
控制了EIP寄存器就相当于控制了进程,我们让EIP指向哪里,CPU就会去执行哪里的指令。
之所以将ESP值装入EBP是因为新的栈帧是位于旧的栈帧上面,所让旧的栈帧的顶部ESP成为新的栈帧的底部EBP