我个人对于栈空间的理解

首先,什么是栈?

堆栈又名栈(stack),它是一种运算受限的线性表。限定仅在表尾进行插入和删除操作的线性表。这一端被称为栈顶,相对地,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素

​ 我相信很多人第一次看到这段话都会觉得玄之又玄,栈是一种后入先出的数据结构,它与队列相反,队列是先入先出的数据结构,你可以把栈想象成一个杯子,先进入栈中的数据最后一个出栈,栈因此有了一个特性,只能在一端操作,也就是只能对栈顶操作

每次入栈的元素称为栈顶元素,而每次出栈的总是栈顶元素,所以栈是一种后入先出的线性表,在小端模式下,栈是由高地址向低地址生长,在大端模式下,栈的生长方向与其相反。一般来说,我们日常使用的计算机采取小端储存,我们默认栈底高地址栈顶低地址

其次,栈有什么用?

​ 栈用于储存函数调用的信息,包括参数局部变量和返回地址。当一个函数被调用时,其相关信息会被压入栈中,函数执行完毕后,这些信息会从栈中弹出,控制权返还给调用者。栈还用于实现程序的控制流,如跳转指令等,由于栈的特殊结构,使得它与堆的配合可以节省内存空间,灵活的进行内存管理和数据处理。

栈和栈帧

​ 在计算机系统中,栈通常是通过CPU寄存器中的栈指针(ESP/RSP/SP)来管理的。每次函数调用时,都会在栈上分配一段内存区域,称为栈帧。当函数执行完毕后,这段内存区域会被释放,栈帧是栈的一个基本单位,也就是说栈由栈帧组成,每个栈帧包含了函数的返回地址,参数,临时变量以及函数调用所需的其他信息。栈帧的结构如下

局部变量表:用于存放函数的非静态局部变量(静态局部变量在离开作用域后值不变,非静态局部变量的值在函数调用结束后就会消失,因为它们所占据的内存空间已经被释放)和编译器自动生成的临时变量

操作数栈 :在执行过程中,操作数栈用于临时存放操作数(操作数指定了指令执行操作时所需的数据或信息的来源)

动态连接:在某些语言中,如Java,栈帧中还包含了动态链接信息,以便在运行时解析方法调用

方法返回值地址:栈帧中保存了函数返回时的地址,以便函数执行完毕后能够正确返回到调用者

一般来说,我们将espebp之间区域当作栈帧栈空间可以拥有多个栈帧,每次调用新的函数,都会生成新的栈帧,在函数调用的过程中,我们将调用函数的函数称为调用者(caller)也叫父函数,被调用的函数叫做被调用者(callee)也叫做子函数,在这个过程中

1.调用者需要知道在哪里获取被调用者返回的值;

2.被调用者需要知道传入的参数在哪里;

3.返回的地址在哪里;

同时我们需要保证,在被调用者返回后,esp,ebp的值与调用前一致,因此我们使用栈来保存这些数据

C语言函数调用栈

栈: 在函数调用时,第一个进栈的是主函数中函数调用后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
当发生函数调用的时候,栈空间中存放的数据是这样的:
1、调用者函数把被调函数所需要的参数按照与被调函数的形参顺序相反的顺序压入栈中,即:从右向左依次把被调函数所需要的参数压入栈;
2、调用者函数使用call指令调用被调函数,并把call指令的下一条指令的地址当成返回地址压入栈中(这个压栈操作隐含在call指令中);
3、在被调函数中,被调函数会先保存调用者函数的栈底地址(push ebp),然后再保存调用者函数的栈顶地址,即:当前被调函数的栈底地址(mov ebp,esp);
4、在被调函数中,从ebp的位置处开始存放被调函数中的局部变量和临时变量,并且这些变量的地址按照定义时的顺序依次减小,即:这些变量的地址是按照栈的延伸方向排列的,先定义的变量先入栈,后定义的变量后入栈;
所以,发生函数调用时,入栈的顺序为:
参数N
参数N-1
参数N-2
…..
参数3
参数2
参数1
函数返回地址
上一层调用函数的EBP/BP
局部变量1
局部变量2
….
局部变量NEBP 基址指针,是保存调用者函数的地址,总是指向函数栈栈底,ESP被调函数的指针,总是指向函数栈栈顶。
首 先,将调用者函数的EBP入栈(push ebp ),然后将调用者函数的栈顶指针ESP赋值给被调函数的EBP(作为被调函数的栈底,movebp,esp),此时,EBP寄存器处于一个非常重要的位置,该寄存器中存放着一个地址(原EBP入栈后的栈顶),以该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数的局部变量值,而该地址处又存放着上一层函数调用时的EBP值;

一般规律,SS:[ebp+4]处为被调函数的返回地址,SS:[EBP+8]处为传递给被调函数的第一个参数(最后一个入栈的参数,此处假设其占用4字节内存)的值,SS:[EBP-4]处为被调函数中的第一个局部变量,SS:[EBP]处为上一层EBP值;由于EBP中的地址处总是”上一层函数调用时的EBP值”,而在每一层函数调用中,都能通过当时的EBP值”向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取被调函数的局部变量值”;

如此递归,就形成了函数调用栈

上述调用栈过程转自函数调用栈 剖析+图解