1 程序中的栈
-
栈在程序中用于维护函数调用上下文
-
函数中的参数和局部变量存储在栈上
-
栈示意图
-
esp:ESP(Extended Stack Pointer)为扩展栈指针寄存器,是指针寄存器的一种,用于存放函数栈顶指针。与之对应的是EBP(Extended Base Pointer),扩展基址指针寄存器,也被称为帧指针寄存器,用于存放函数栈底指针。
ESP为栈指针,用于指向栈的栈顶(下一个压入栈的活动记录的顶部),而EBP为帧指针,指向当前活动记录的底部。
-
当不需要使用某一块栈内存时,esp 后退,对应着
pop
操作
-
-
栈保存了一个函数调用所需的维护信息
- 参数
- 返回地址
- 局部变量
- 调用上下文
- 。。。
-
函数调用过程
-
每次函数调用都对应着一个栈上的活动记录
- 调用函数的活动记录位于栈的中部
- 被调函数的活动记录位于栈的顶部
-
-
函数调用的栈变换
-
从
main()
开始运行 -
当
main()
调用f()
-
当从
f()
调用中返回main()
-
-
函数调用栈上的数据
- 函数调用时,对应的栈空间在函数返回前是专用的
- 函数调用结束后,栈空间将被释放,数据不再有效
-
示例:指向栈数据的指针——错误行为
-
Demo1
#include <stdio.h> int* g() { int a[10] = {0}; return a; //返回一个局部数组 } void f() { int i = 0; int b[10] = {0,1,2,3,4,5,6,7,8,9}; int* pointer = g(); for(i = 0;i < 10;i++) { b[i] = pointer[i]; } for(i = 0;i < 10;i++) { printf("%d ",b[i]); } } int main() { f(); return 0; }
-
编译
test.c: In function 'g': test.c:7: warning: function returns address of local variable
-
运行
0 0 0 0 0 0 0 0 0 0
-
Demo2:验证栈空间的数据在函数调用后会被修改
#include <stdio.h> int* g() { int a[10] = {0}; return a; //返回一个局部数组 } void f() { int i = 0; int b[10] = {0,1,2,3,4,5,6,7,8,9}; int* pointer = g(); for(i = 0;i < 10;i++) { printf("%d ",pointer[i]); } } int main() { f(); return 0; }
-
编译运行
- 分析:
g
函数调用完毕后,其栈空间仍然在那,只是 esp,ebp 指针移动了位置而已。但当下一个函数调用后,即printf
函数调用后,需要在栈上建立一个新的记录,原先g
函数的栈空间的数据会因此发生变化,也就是说原先的活动记录被销毁了。
0 12976116 0 0 -1081184744 11856928 -1081184664 11856928 12977376 134514000
- 分析:
-
2 程序中的堆
-
堆是程序中一块预留的内存空间,可由程序自由使用
-
堆中被程序申请使用的内存在被主动释放前将一直有效
-
问题:为什么有了栈还需要堆?
- 栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如:局部数组
-
C 语言程序中通过库函数的调用获得堆空间
- 头文件:
malloc.h
malloc
:以字节的方式动态申请堆空间free
:将堆空间归还给系统
- 头文件:
-
程序对堆空间的管理方式
-
空闲链表法,位图法,对象池法等
-
malloc
返回的内存大小可能会比申请的要大一点,因为以空闲链表法管理堆空间时,寻找到的是最接近的那个空间,一般都是大于等于所需的内存大小
-
3 程序中的静态存储区
-
静态存储区随着程序的运行而分配空间
-
静态存储区的生命周期直到程序运行结束
-
在程序的编译期静态存储区的大小就已经确定
-
静态存储区主要用于保存全局变量和静态局部变量
-
静态存储区的信息最终会保存到可执行程序中(.exe 或者 .out 文件中)
-
示例:静态存储区的验证
-
Demo
#include <stdio.h> //全局变量 int g_v = 1; //静态全局变量:只在当前文件中可见 static int g_vs = 2; void f() { //静态局部变量 static int g_vl = 3; printf("%p ", &g_vl); } int main() { printf("%p ", &g_v); printf("%p ", &g_vs); f(); return 0; }
-
编译运行:可以发现,这三个变量的地址是连续的,都是存放在程序的静态存储区
0x804a014 0x804a018 0x804a01c
-