浅析堆栈和内存溢出

对于一个PHPer来说,PHP已经帮我们处理好了GC,线程安全等内存相关的操作,完全不需要我们去考虑,让我们更加注重于代码的实现。再加上大学原本就是混的嗨皮,学的心塞,也对这些概念没什么了解,但是近期在看PHP的源码的时候,感慨还是逃不掉这些概念。

内存分布

一个C语言的内存布局:

  1. 栈区(stack): 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆区(heap) : 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
  3. 全局区(静态区)(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。
  4. 文字常量区 : 常量字符串就是放在这里的。 程序结束后由系统释放
  5. 程序代码区 : 存放函数体的二进制代码。

内存分布如下图所示

img

上图是网上搜索堆栈很常见的一个图,在这个图里面,栈区和堆区就是对应内存分配中的栈区和堆区,其余的可参照下面对应

可读写取 => 全局区

文字常量区 => 只读区

程序代码去 => 只读区

例子

下面,我们结合网上非常经典的一个例子来分析

https://github-1253518569.cos.ap-shanghai.myqcloud.com/1470646448_1244.PNG

  • int a = 0 : 这里是写在main函数外面的全局变量,属于初始化过的全局变量,所以应该存在在 全局区
  • char *p1 : 这里同上,区别是没有经过初始化,所以属于未初始化的全局变量,所以应该也存在 全局区
  • int b : 这一行是在main函数里面,也没有声明全局变量,属于局部变量,所以应该是存放在 栈区 上面
  • char s[] = “abc” : 同上,s 变量是 局部变量,也就是存放在 栈区 上面,分配的内存里面存储的就是 “abc”
  • char *p2 : 同 int b 分析可得,存放在 栈区 上面
  • char *p3 : 同上分析可得, p3 是存放在 栈区 上面,p3所在的内存就存储了它所指向的地址,也就是说并不是存储的”123456”,这时候 “123456” 就可以看成一个常量字符串,应该是存放在 文字常量区
  • static int c = 0 : 初始化的静态变量应该存放在 全局区
  • p1 = (char * )malloc(10) : p1 这个变量肯定是被压入 栈内 的,其值也就是申请的内存的首地址, 那 malloc 分配的地址呢, 这个地址是由程序员分配的,所以是存放在 堆区 上的一块内存
  • p2 = (char *) malloc(20) : 同上分析
  • strcpy(p1, “123456”) : 对于编译器的优化,个人并不清楚,所以可依据图中的注释

内存泄漏

在上面的内存分布中会发现,除了堆区以外,其他区的内存区会在程序结束时,由系统释放,所以内 内存泄漏是指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

所以,在C语言中,内存的释放是由 free() 来处理完成的,

1
2
3
4
5
6
7
8
9
10
11
void free(void *ptr)  
{
struct mem_control_block *free;
free = ptr - sizeof(struct mem_control_block);
free->is_available = 1;
return;
}
struct mem_control_block {
int is_available; //这是一个标记?
int size; //这是实际空间的大小
};

上面是 free(), 其实是把对应内存块设置为可用,同时把指针再向前偏移 内存块控制头 的长度,也就是实际分配的内存块首地址,所以,这里引出一个问题,如果我们给 free() 的不是malloc() 返回给我们的地址,这时候去释放内存是不时会存在问题 ,所以,综上,内存泄漏可以总结出两种原因

  1. malloc 之后 没有 free
  2. free 是 给的地址不是 malloc 返回给我们的首地址