C语言剖析—程序中的堆栈
参考自 狄泰 C语言进阶
个人认为堆栈可以分为两个概念 一个是数据结构中堆栈概念 一个是计算机程序存储的概念
这里分析的堆栈就是从计算机存储的概念出发
首先栈是用来程序自主分配用来保存一些局部变量等
同时栈也是维护函数调用的上下文信息
栈是保存了函数哪些相关信息? 看下图
上图就描述了栈保存一个函数的记录 也叫活动记录 其中
参数 --就是函数传入的参数
返回地址 --就是函数结束返回调用处的地址
ebp --指针指向函数返回的地址
old ebp --上一次返回的地址
esp --指针指向栈顶
那么栈是怎么管理函数调用返回呢
其实是通过ebp esp指针的偏移来实现函数调用返回,
怎么实现? 可以i通过下面代码分析:
f(){}
int main()
{
f();
return 0;
}
这里 main函数调用 f() ebp 和esp指针偏移指向 f()函数对应的返回地址的栈顶
当 f() 函数调用结束 ebp指针通过指向的地址返回main并指向main函数结束地址 , 同理esp指针指向调用结束后的栈顶
OK 这里的过程就是栈管理函数活动记录的大概流程
那么这有个问题:为什么函数不能返回局部变量呢
原因就是栈对于函数调用管理是通过指针指向的方式, 调用结束返回后原本的空间没有被清楚还保存旧的数据, 但是下一次调用其他函数时候这片空间就可能被占用了,所以局部变量的内存地址的内容就没有意义无效了每当一个函数结束返回后,其对应的地址空间内就被释放数据就不再有效了,
怎么证明?
可以用以下代码解释:
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int i;
int b[5] = {1,2,3,4,5};
int* pointer = g();
for(i = 0; i < 5; i++)
{
b[i] = pointer[i];
//printf("%d ", pointer[i]);
}
for(i = 0; i < 5; i++)
{
printf("%d ", b[i]);
}
}
int main()
{
f();
return 0;
}
结果是
0 0 0 0 0
这就证明 函数结束返回后原地址的数据没有被清理
那么继续:
#include <stdio.h>
int* g()
{
int a[10] = {0};
return a;
}
void f()
{
int i;
int b[5] = {1,2,3,4,5};
int* pointer = g();
for(i = 0; i < 5; i++)
{
printf("%d ", pointer[i]);
}
}
int main()
{
f();
return 0;
}
这里代码稍作修改 不直接输出打印
结果就是
0 -64537 0 0 -23233
就是一堆无效数据 这是为什么呢 原因就是 g()函数返回后马上调用了printf函数输出所以 g()函数占用的堆空间就被printf函数占用了 原先的局部变量的空间内容就被覆盖无意义了
接下来是 堆
堆就是预留出来程序员自主分配使用的空间,申请的堆空间需要释放
这里就有个问题:有了栈,为什么还需要堆?
*原因我认为是这样的: 因为在开发中,可能需要一片空间来使用, 但是我们知道栈空间是不能返回的所以就有了堆空间
OK 接下来说下堆是怎么样管理空间的
一般来说堆空间的管理方式会由于不同的系统不同的管理方式:有空闲链表法,位图法,对象池等
这里就讲空闲链表法:
这个方法就是将空间以链表方式一个一个链接起来,每个节点的空间大小是不同的,当我们使用mallco()函数申请空间时候 会根据传入参数大小 寻找最接近大小的节点并取相应的空间地址返回
比如说此时申请 int 类型大小个字节 (4字节) 那么就会在链表中遍历 寻找最接近4字节大小的节点, 这里寻找到5字节空间就返回此处空间地址,
*我们就会发现其实malloc 函数申请到的空间有可能比我们想申请的空间大小要大
OK 先记录这么多…