C语言深处

    返回首页    发表留言
本文作者:李德强
          第二节 堆与申请内存
 
 

        堆与栈是C程序中非常重要的内存空间,它们都是操作系统在程序运行是为其分配的一部分内存空间,只不过他们在实现原理和作用上不同罢了。前面章节中提到过堆。在C语言中动态分配内存通常是通过堆来实现的。我们知道,当程序并操作系统载入内存后,被称作进程,进程可以使用操作系统为其分配的堆栈空间。假设程序在没有申请动态内存空间时堆的大小为1024B(这个数值很小,只是为了说明问题,实际上堆的内存空间很大、很充足),它在内存中的结构如下:


 

        操作系统会分配一个指针变量p指向堆可用空间的起始位置,这个地址第1个4字节存放了下一个空闲空间的地址,第2个字节存放了这个空闲空间的大小。当使用malloc()函数来申请内存时系统会从堆的头部p指针开始查找,找到一个符合申请需要的空闲内存空间,并返回其首地址。例如申请4个字节的内存空间:

void *p1 = malloc(4);

        申请内存后堆的内容如下:


 

        操作系统的内存分配算法如下:

  1. 通过p指针指向的4字节地址,这个地址中存放了下一个空闲地址0x0。
  2. 检查其后的4字节1020,表示可以有1020个字节可用,则为其分配4个字节的内存空间,如内存空间不足则向下查找。
  3. 将找到的第1个4字节标记为申请内存大小。
  4. 将下一处4字节地址标记为空闲起始地址(0x8)。
  5. 将下一处的第2个4字节地址标记为空闲空间大小1012。
  6. 查找地址返回给程序,返回的是0x4。

        接下来再进行内存申请:

void *p2 = malloc(8);

        结果返回的是0xc,堆图如下:


 

        接下来再进行内存申请:

void *p3 = malloc(4);

        结果返回的是0x14,堆图如下:


 

        当调用free函数进行内存释放时,操作系统会根据对free函数的传入参数进行处理,查找这个地址的前4字节,因为申请内存时返回的地址前4个字节中记录着申请的内存大小。于是操作系统将按此大小释放内存空间。例如:

free(p2);

        内存堆图如下:


 

        注意,当执行内存释放操作时被释放的内存中的前8个字节会有特殊的记录,前4字节记录着下一处空闲空间的地址,即0x1c,第2个4字节记录着这一块空闲空间的大小0x8字节。而对于0x1c处的地址也将被记录为下一个空闲空间地址即0x8。

        我们接下来再释放p3所占用的内存:

free(p3);

        内存堆图如下:


 

        同样,操作系统释放了p3之后0x14处的内存地址被释放,但这一块内存也变成了空闲空间,它的下一个空闲空间地址为0x1c,而0x1c处的下一个空闲空间为0x8,而0x8处的下一个空闲空间为0x14。

        另外,当操作系统释放了一块内存空间之后如果出现了两个相临的空闲空间,有的操作系统会将其合并成一个整体块,而大多数操作系统并不会这样处理。

        当进程动态内存释放时,通常是将内存地址传入free函数,操作系统会认为这个地址的前4字节存放了申请内存的大小,并按这个大小进行内存释放操作。如果我们在内存释放时,传入一个异常的地址呢?比如:

#include <stdlib.h>

int main(int argc, char **args)
{
	void *p = malloc(8);
	free(p + 4);

	return 0;
}

        这条语句是合法的,但是在程序运行时会产生非常混乱的结果:

*** Error in `./main': free(): invalid pointer: 0x08fb900c ***

        当操作系统为程序释放内存时,操作系统会认为这个地址的前4字节就是申请内存的大小,无论这个地址是不是当初申请的那个。所以申请和释放内存是应格外的小心谨慎。一个小小的疏忽就可能导致整个程序的崩溃。

    返回首页    返回顶部
#1楼  none  于 2017年04月11日04:49:23 发表
 
请问内存释放时,如何更新所有相关的下一空闲地址,是否另外需要维护一个链表
#2楼  李德强  于 2017年04月13日11:24:09 发表
 
操作系统会认为使用malloc内存申请和free内存释放的使用者是一个很优秀的程序编写者,使用者会很小心的申请内存并释放它们,如果你申请了10个内存地址,那么操作系统认为你的程序在某处会准确的释放这10个内存地址。

不过话又说回来,如果某个程序出现内存泄漏(没释放内存地址),或使用的非法地址,其实只会影响这个程序,操作系统会杀死这个进程,并释放此进程占用的所有内存。对其它进程没有任何影响(请参见《跟我一起写操作系统》第九章内存管理)。
  看不清?点击刷新

 

  Copyright © 2015-2018 问渠网 辽ICP备15013245号