栈区
内存中的栈区实际上类似于数据结构中的栈。两者区别的是数据结构中的栈是解决为程序开发而设计的算法问题(例迷宫求解问题),而在内存中的栈区是解决内存分配的问题(以前堆栈的说法就是指内存的栈区,也叫系统栈)。不过,它们俩相同点就是遵守先进后出的规则。下面讲解的是程序运行时候所说的栈区,并不特指定目标文件的栈段。
栈区的组成部分:在栈区里面其实又可以分成好几个区域,他们叫做栈桢,一个栈桢就是一个函数,需要调用该函数的时候就入栈,函数return的时候就会弹出栈,所以他们的生命周期从这个函数开始到函数结束。
栈桢里面存放这以下几种东西:参数变量的地址、局部变量的地址、return的地址、栈指针和基指针等。
栈区有重要的寄存器:
栈顶指针寄存器ESP:永远指向栈桢的栈顶的指针
基址寄存器EBP(栈底寄存器EBP):永远指向栈桢的底部。存取某时刻的栈顶指针,以方便对咋还能的操作,如获取函数参数、局部变量等 。
eax寄存器:一般用来保存函数的返回值 (关于栈寄存器的知识,下面会提到的 )
栈有两个基本操作:入栈(也叫压栈)和出栈(也叫弹栈)。
由栈区分为几个小区域--几个栈桢(也就是函数)也称为函数栈。所以调用某函数的时候,函数栈也遵守栈的先进后出的规则。
以下的例子是函数1、函数2和函数3逐步被调用的情况:
(其中因为主函数main作为程序入口,是第一个被放进栈里面的。以下是两种代码中的被调用函数入栈的顺序是不一样的,自己分析一下)
代码一和图解:
代码二和图解:
函数fun1内部的自动变量以及形参的入栈情况:
函数fun1内部的变量int d如果放改变在fun2()后面,分配的地址是一样的。因为栈区分了几个区域(函数栈),一个函数栈里面包含所有自动变量都是放在同样函数栈区域的。
形参和实参的入栈顺序是这样子的:
fun1(int a,int b )和int c,d的入栈顺序是按照自右向左读取,所以地址:右边的变量地址>左边的变量地址。(向低地址生长入栈)
(改正:下面的代码中:把注释“当int b放在...”改为“当int d放在... ”)
函数栈1的各个变量入栈与出栈图解:
以下一段文字来自于某博客文章的内容(还是别人写得好):
*-------------------------------------------内容界线---------------------------------------
函数调用大概包括以下几个步骤:
(1)参数入栈:将参数从右向左依次压入系统栈中。
(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
(3)代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
(4)栈帧调整:具体包括:
<1>保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)。
<2>将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部)。
<3>给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶)。
<4>对于_stdcall调用约定,函数调用时用到的指令序列大致如下:(push是入栈,pop出栈)
push 参数3 ;假设该函数有3个参数,将从右向做依次入栈
push 参数2
push 参数1
call 函数地址 ,call指令将同时完成两项工作:
a)向栈中压入当前指令地址的下一个指令地址,即保存返回地址。
b)跳转到所调用函数的入口处。
push ebp ;保存旧栈帧的底部
mov ebp,esp ;设置新栈帧的底部 (栈帧切换)
sub esp,xxx ;设置新栈帧的顶部 (抬高栈顶,为新栈帧开辟空间)
函数返回的步骤如下:
<1>保存返回值,通常将函数的返回值保存在寄存器EAX中。
<2>弹出当前帧,恢复上一个栈帧。具体包括:
(1)在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。
(2)将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。
(3)将函数返回地址弹给EIP寄存器。
<3>跳转:按照函数返回地址跳回母函数中继续执行。
还是以C语言和Win32平台为例,函数返回时的相关的指令序列如下:
add esp,xxx ;降低栈顶,回收当前的栈帧
pop ebp ;将上一个栈帧底部位置恢复到ebp
retn ;a)弹出当前栈顶元素,即弹出栈帧中的返回地址,至此,栈帧恢复到上一个栈帧工作完成。b)让处理器跳转到弹出的返回地址,恢复调用前代码区
5.寄存器与函数栈帧
每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。Win32系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。
(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。
(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。