这里写目录标题
- 栈
- 概念
- 栈的初始化
- 栈的溢出
- 函数的栈帧
- 函数的返回
栈
概念
英文:stack,也叫做堆栈。
特点:先进后出。
栈的两个基本操作,也就是入栈和出栈。都是通过SP指针来维护。C语言中的函数的局部变量,传递的实参,返回的结果、编译器生成的临时变量都是保存在栈中。
栈的初始化
系统一上电,开始运行的都是汇编代码,在跳到第一个C语言函数运行之前,都要先初始化栈空间-----将栈指针指向内存中的一段空间,就完成了栈的初始化。
ARM处理器是使用SP寄存器和FP寄存器管理堆栈。
在LINUX环境下,栈的起始地址就是进程用户空间的最高地址,栈指针从高地址指向低地址增长。
栈的溢出
Linux中默认的每一个用户的进程时分配8MB的空间大小。
#include <stdio.h>
int main(void)
{
char a [ 8 *1024 *1024 ];
int i ;
……
};
这种情况下,数组a保存在栈中,占用了8MB的栈空间,那这样别的局部变量i就没有存储空间了,就会造成了栈溢出。
为了避免栈溢出:
- 尽量不要使用的大的数组;
- 函数的嵌套层数不宜过深。
函数的栈帧
函数的栈帧除了保存局部变量与实参,还用来保存函数的上下文。每每调用过一次函数便会创建一个栈帧。
当函数运行完毕,栈帧将会销毁。
简单来说,就是使用EBP和ESP(被调用函数指针,总是指向函数栈顶)指针:
#include<stdio.h>
int add(int a, int b)
{
int c = 0;
c = a + b;
return c;
}
int main()
{
int a = 1;
int b = 1;
int sum;
sum = add(a, b);
return 0;
}
- 使用护当前ebp:
由于我们马上要创建新的栈帧空间,因此ebp和esp都得将变动,为了能够让我们调用完add函数后还能让ebp回到当前位置我们需要对ebp的值进行保护,即将此时ebp的值压入栈(至于为什么不需要保护esp,看到后面你就能明白)
- 创建所需调用函数的栈帧空间
令ebp指向当前esp的位置并根据add函数的参数个数,创建一个大小合适的空间。
3. 创建空间
4. 保存局部变量
将add函数中创建的变量"int c = 0"放入刚刚开辟的栈帧空间中
- 参数运算
根据形参与局部变量,进行对应的运算,这里执行"c = a +b", 得到 c = 2,放入刚才c对应的位置。
到这里,函数执行过程就结束了。
函数的返回
1.存储返回值
现在我们已经达成了目的"add(a,b)“,要将之前创建的add的函数栈销毁,以使得我们能够回到main函数中正常执行,而在销毁add的函数栈帧前我们的main函数可还没有拿到运算结果,因此我们需要先将需要返回的值存储起来,存储的位置就是前面提到的eax寄存器,这里"return c”,我们将c的值放到eax寄存器中。
-
销毁空间
拿到了运算结果后,我们就没有任何任何顾虑了,可以直接销毁函数的栈桢空间了。
-
ebp回上一栈帧栈底
此时ebp拿到之间存储的上一栈帧栈底的值,回到相应的位置,于此同时,存储的ebp没有用了,也将被销毁。
-
销毁形参
形参也不再有用,因此也随即销毁。
-
main函数拿到返回值
参考好文:
https://blog.csdn.net/Zero__two_/article/details/120781099