程序中栈
- 栈是现代计算机程序里最为重要的概念之一
- 栈在程序中用于维护函数调用上下文
- 函数中的参数和局部变量存储在栈上
- 栈保存了一个函数调用所需的维护信息
在程序中,栈是一种行为,先进后出;
其它数据信息:函数调用时产生的临时变量等;
ebp 指向函数调用后的返回地址
esp 栈顶指针。
函数调用过程
每次函数调用都对应着栈上的活动记录
- 调用函数的活动记录位于栈的中部
- 被调函数的活动记录位于栈的顶部
函数调用的栈变化 一
- 从 main() 开始运行
函数调用的栈变化 二
当 main() 调用 f()
函数调用的栈变化 三
- 当从 f() 调用中返回 main()
ebp 指针向前读 4 个字节,即 esp 指针所需要的返回地址;
ebp 指针向后读 4 个字节,即 ebp 指针之前所指向的地址, ebp 返回。
函数调用栈的数据
- 函数调用时,对应的栈空间在函数返回前是专用的
- 函数调用结束后,栈空间被释放,数据不再有效
编程实验:指向栈数据的指针
Test_1.c
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int i = 0;
int* pointer = g();
for(i=0; i<10; i++)
{
a[i] = pointer[i];
}
for(i=0; i<10; i++)
{
printf("%d\n", a[i]);
}
}
int main()
{
f();
return 0;
}
编译输出:
main.c: In function ‘g’:
main.c:7: warning: function returns address of local variable
运行输出:
0
0
0
0
0
0
0
0
0
0
分析:
g() 函数返回时, pointer 指向的栈空间的int[10]并没有立即发生改变,输出原始值。
Test_2.c
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int i = 0;
int* pointer = g();
for(i=0; i<10; i++)
{
printf("%d\n", pointer[i]);
}
}
int main()
{
f();
return 0;
}
编译输出:
main.c: In function ‘g’:
main.c:7: warning: function returns address of local variable
运行输出:
0
16117748
0
0
-1077848536
14998560
-1077848488
14998560
16119008
134513936
分析:
pointer 指向的栈空间的int[10]发生改变,输出非原始值。
两次输出结果不同,发生了什么?
g() 函数返回时,所对应活动记录被释放,但此时并没有发生函数调用,因此数据没有发生改变。
当调用 printf 函数时,printf 函数需要在栈上建立对应的活动记录,g() 之前的活动记录因此被改变,所以指针指向的内存空间值发生改变。pointer 指向的内存空间不在有意义,成为野指针。
不能返回局部变量的地址和局部数组的数组名!
程序中的堆
- 堆是程序中一块预留的内存空间,可由程序自由使用
- 堆被程序申请使用的内存在被主动释放前一直有效
为什么有了栈还需要堆?
栈上的数据在函数返回后就会被释放掉,无法传递到函数外部,如:局部数组
C 语言程序中通过库函数调用获得堆空间
- 头文件: malloc.h
- malloc 以字节的方式动态申请堆空间
- free 将堆空间归还给系统
系统对堆空间的管理方式
- 空闲链表法,位图法,对象池法等
分析:
- 每个节点下的内存都为 12byte、100byte、50byte....
- 当申请内存时,系统遍历空闲链表节点,查看所需内存大小与哪一个节点下内存大小最接近
- 到此节点下查找可用单元,查找到之后返回对应单元地址
在空闲链表管理法中,存在查找所需内存大小与哪一节点最接近的操作,这将导致 malloc 实际分配的内存可能会比请求的多。但我们不能依赖这种行为,因为在不同的系统中,对堆空间的管理方式可能是不同的。
程序中的静态存储区
- 静态存储区随着程序的运行而分配空间
- 静态存储区的生命周期直到程序运行结束
- 在程序的编译期静态存储区的大小就已经确定
- 静态存储区主要用于保存全局变量和静态变量
- 静态存储区的信息(大小信息...)最终会保存待可执行程序中
编程实验: 静态存储区的验证
#include <stdio.h>
int g_v = 1;
static g_vs = 2;
void f()
{
static int g_v1 = 3;
printf("%p\n", &g_v1);
}
int main()
{
printf("%p\n", &g_v);
printf("%p\n", &g_vs);
f();
return 0;
}
输出:
0x804a014
0x804a018
0x804a01c
分析:
在不同地方定义的全局变量、静态局部变量顺序排放在一起;
静态存储区的大小及位置在编译期就被确定。
小结
栈,堆和静态存储区是程序中的三个基本数据区
- 栈主要用于函数调用的使用
- 堆主要用于内存的动态申请和归还
- 静态存储区用于保存全局变量和静态变量
以上内容参考狄泰软件学院系列课程,请大家保护原创!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。