malloc的内存分配原理

0 堆内存的在计算机内存中的形式

根据《The C Programming language》推测得到堆内存,图中的Heap区域即为堆内存块(Heap区域的数目不代表计算机堆内存的真实数目)。

 

[1] 堆内存不连续。只有标识为Heap的才是堆内存。

[2]  在malloc()/free()看来,每个Heap所代表的的堆由两部分组成:Header +可给用户使用的堆内存。在Header中包含了“指向下一邻近高地址堆内存块的指针”、“本堆块的大小”。每次由malloc()函数分配给用户的堆内存也必须包含Header结构(且所占内存就在返回给用户使用的堆内存之前),这样是为了让malloc()/free()更好的管理堆内存。

[3] malloc()/free()函数操作的堆内存是如图所示的一个链(Heap1 -> Heap2 ->Heap3 ->Heap4 ->Heap1),可通过此链表访问到任意一段堆内存。所以,经malloc()函数实际分配得到的堆内存要比用户实际需求的要大一个Header,只是返回给用户的堆内存大小刚好是用户所需。free()释放时,也要根据Header的内容将此段曾供给用户使用过得堆内存释放到最邻近的一个堆块中去。

 

 

这就是内存中的堆内存。堆内存由用户用代码分配及回收。堆和栈的区别不仅在于内存的存在形式,在使用时栈一般拥有内存名即栈内存可以由内存名(变量名)直接访问,也可以通过地址(指针)访问栈内存。但对于堆内存来说,堆不存在内存名,只有通过地址(指针)访问。

 

1堆内存

Figure1:内存中的堆内存空间

假设从《The  C  Programming  Language》中推测正确,从未经动态分配的堆内存呈现上图形式。不连续的堆内存以“链”的形式联系:Heap1 -> Heap2 ->Heap3 ->Heap4->Heap1。笔迹将构成“堆链”的每个堆内存(如Heap1)称为“堆块”。malloc()/free()将每个堆块看作由两部分构成:“Header”和“可用堆内存”。在Header中包含了“指向下一个堆内存块的指针”、“本堆块的大小”。这样malloc()/free()就能更好地管理堆。

2 堆内存分配

[1] mallco()分配机制

根据C中malloc(n)函数动态分配堆的机制:分配堆内存的时候就依序由低到高的地址搜索“堆链”中的堆块,搜索到“可用堆内存”满足n的堆块(如Heap1)为止。若Heap1的“可用堆内存”刚好满足n,则将Heap1从“堆链”中删除,同时重新组织各堆块形成新的“堆链”;若Heap1的“可用堆内存”大小大于n,则将malloc(n)申请到的“Header” + "可用堆内存"部分从Heap1中分裂,将剩余的Heap1堆内存块重新加入“堆链”中。经分裂后的堆内存也包含“Header”和“可用堆内存”两部分(如图Figure 2),然后将由malloc()分配得到的“可用堆内存”返回给用户。若某块堆内存空间比较大(如Heap1),能够满足较小内存的多次申请,那么由malloc(n)多次申请的堆内存块都是连续被分配给用户的(因为有Header,所以用户使用的堆地址不连续)

 

为方便free()释放堆空间,经malloc(n)分配给用户的堆空间也隐含一个Header。如下图所示:

Figure2:malloc()分配的堆内存结构

由于Header的构成的内存对齐,C中malloc(n)函数分配的堆内存会大于等于Header + n。

3 malloc()分配内存

可先参见位经malloc()函数申请分配的堆内存在计算机中的形式:计算机中的堆

 

经malloc()分配过得堆内存结构如下:

Read From《The C Programming Language》。

 

可用的堆内存块以“可用堆内存链表”的形式存在。malloc()进行动态分配的特点:

  • malloc()根据用户所需分配内存的大小n (bytes)在“堆链表”(见未使用过得堆内存)里搜索。直到搜索到一个大于等于n字节的堆内存块为止。如果此堆内存块的大小刚好为n,则直接将首地址返回给用户;如果此内存块的大小大于n,则将此块堆内存分裂,将大于n部分的堆内存留在可用堆内存中,以“堆链表”的形式和其它未分配的堆内存发生联系。
  • 如果整个堆链表所代表的堆内存块都没有大于等于n的堆内存块,系统将给“堆链表”链接一个更大的区域供其使用。要是这一步也失败了,malloc()函数就返回NULL给用户。

malloc()函数分配内存成功则返回可用堆内存块的首地址,若分配失败则返回空。在使用malloc()后一定要判断堆内存是否成功。若对内存分配未成功使用指针操作内存也会使程序出现异常。动态分配内存时要采取以下结构:

 

[cpp] view plaincopyprint?

 

  1. char *pL =NULL;  
  2. ……  
  3. pL       = (char *)malloc( sizeof(char) * size);  
  4. if(pL)  
  5. {  
  6.       …  
  7. }  

 

分配成功后,得到的堆内存首地址一定要保存,不然后来无法释放堆内存而造成内存泄露。而且不可使用未初始化的pL指向的内存块。

 

4 用指针来使用堆空间

  • 定义指针后,释放堆空间后都应将指针赋值为NULL。若指针之上有地址值,而以此地址值为起始地址的内存空间不再可用,则就形成了野指针,野指针有潜在的危险。
  • 在上一点的基础之上,使用指针前判断其值是否为NULL。
  • 以指针为索引(堆内存无名),若malloc分配内存成功,初始化堆内存(malloc时,大小要不为0)。malloc前的强制转换类型规定了申请的堆内存将要存的数据类型。
  • free堆内存后,指针保存的地址值还在,只是那块内存已经被回收了,所以需要再次将指针的值设为NULL,避免使用野指针。free内存时,按照逻辑来,防止内存泄露。

 

指针名所代表的4 bytes内存上存了堆内存的首地址后,访问这块堆内存内容跟平时使用指针差不多。可以以指针的形式访问(甚用p++ || ++p,堆内存首地址可不要丢失,留着释放),也可以使用下标的形式访问。

5 free()内存

当使用free()函数释放堆内存的时候,free()函数将堆内存插入到于要释放堆内存地址最邻近的一个位置上,尽可能的使堆内存以大块的形式存在而不至于让堆内存称为碎片。

 

释放未指向任何堆内存块的指针也会造成内存泄露。所以在释放指针前的一个基本操作是判断指针内容是否为空,free(p)后只是将p指向的内存回收,p的值依旧存在,为避免再次使用p的值还需要将p赋值为NULL(因为使用指针前都会判断是否为NULL)。释放堆内存块采取这样的程序结构:

 

[cpp] view plaincopyprint?

 

  1. if(pL)  
  2. {  
  3.       free(pL);  
  4.       pL      = NULL;  
  5. }  

6 指针赋值为NULL的道理

有笔记“C中的void和NULL”表面引用NULL指针的后果。为了更好的利用指针,避免野指针(指针所指的内存块不可用)的使用在所有使用指向堆内存块的指针前都采取如此的结构:

 

[cpp] view plaincopyprint?

 

  1. if(p)  
  2. {  
  3.       //通过指针操作堆内存  
  4. ……  
  5. }  

定义指针后将其值赋值为NULL。此时指针指向的内存地址为NULL,NULL对指针的赋值是将指针置成空指针(什么也没有指向)还是将指针指向了一段特殊的地址取决于编译器,编程中我们不需要了解NULL到底代表什么,只需要用NULL来避免指针带来的后果。

 

定义指针后将其赋值为NULL之后的好处在于避免系统给予局部指针变量的随机值,我们在使用指针前(malloc()除外)都判断一下指针的值是否为NULL,只有在不为空的情况下才能对此进行操作,如free(p),若在不判断p是否为空的情况下进行free(p)操作则会造成内存泄露。

7 在含指针参数的函数内使用断言

(1)用断言判断指针是否为NULL

判断指针是否为NULL的主要针对对象是指向堆内存的指针。比如在以下内存拷贝函数中:

 

[cpp] view plaincopyprint?

 

  1. flag  my_strcpy(char  *StrTo,  char  *StrFrom)  
  2. {  
  3.       if(!StrTo || !StrFrom)  
  4.       {  
  5.             return  -1;  
  6.       }  
  7.       char  *StrToL, *StrFromL;  
  8.       StrToL      = StrTo;  
  9.       StrFromL    = StrFrom;  
  10.       while(*StrToL++ = * StrFromL++)  
  11.             NULL;  
  12.       return 0;  
  13. }  

 

程序中首先判断两个地址是否为空。判断StrTo是为了了解StrTo是否指向一段空间。当然若StrTo指向一个常数,往后拷贝操作还得出错。

 

像这样带指针参数的子函数内都很有必要有这么一段判断指针是否为NULL的语句,故而可以将这样的代码写成函数来供大家使用,再考虑此代码段比较小可以用宏代替。这样的(带参数)宏可称为断言,因为当指针未空时就退出子函数(如assert())。

 

如以上一段判断子函数是否为空可以用如下宏代替,形成一个断言:

 

[cpp] view plaincopyprint?

 

  1. #define  MY_ASSERT(pStrTo, pStrFrom)     if(!StrTo || !StrFrom)     \  
  2.                      {                          \  
  3.                                               return  -1;           \  
  4.                      }  

 

然后在每个程序中直接调用MY_ASSERT(pStrTo, pStrFrom);即可。由于这样的宏(断言)可能供许多函数的使用,所以一定要保证它的正确性。

 

(2)内存块重叠

内存块重叠指多个指针指向的内存有重叠的情况。对内存块的操作是否会影响源内存块的内容(如内存数据拷贝)。

两指针指向的内存块重叠

如上图将p2指向内存的数据拷贝给p1代表的内存中去后,p2指向的内存块数据也被改变。堆内存块的操作不要有副作用。

 

8 总结

(1)用NULL(因其特殊性)来统一标识指针的可用性。使用指针前都应该判断一下指针是否为NULL。

(2)将局部指针变量初始化为NULL(消除系统给其赋的随机值,系统为其赋随机值也就造就了野指针)。

(3)指针用于指向堆内存时需要注意:

  • malloc()后一定要判断是否malloc()成功。malloc()成功后一定要保存所分配堆内存块的首地址。
  • 使用堆内存块前要初始化。
  • 使用堆内存块不可越界。
  • 正确释放每个堆内存块。且释放后将指针的值重新赋值为NULL。

(4)使用指针前都应该判断一下指针是否为NULL。

 

 

完全使用完某个指针或释放指向堆内存的指针后,将其值赋值为空。指向堆内存的指针在释放完需要赋值为空的理由见free ()堆内存。对于指针定义时初始化和完全使用完指针后再将其值赋为NULL的道理在于所有使用指针的语句前都会有有判断指针是否为NULL的语句。尤其是在子函数内判断指向堆内存块的指针实参是否为NULL

9 malloc()分配总结

对于C中的malloc(n)分配,有以下进一步的结论:

  • 实际分配的堆内存是Header + n结构。返回给用户的是n部分的首地址。
  • 由于内存对齐值8,实际分配的堆内存大于等于sizeof(Header) + n。

所以在编程过程中,需要用内存对齐的知识合理的让malloc()分配的内存变得小一些。

转载:http://blog.csdn.net/misskissc/article/details/17717717

时间: 2024-09-23 14:58:59

malloc的内存分配原理的相关文章

linux环境内存分配原理 mallocinfo【转】

转自:http://www.cnblogs.com/dongzhiquan/p/5621906.html Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢 ? Linux 的虚拟内存管理有几个关键概念: 1.每个进程都有独立的虚拟地址空间,进程访问的虚拟地址并不是真正的物理地址: 2.虚拟地址可通过

理解Javascript_01_理解内存分配原理分析_javascript技巧

原始值和引用值 在ECMAScript中,变量可以存放两种类型的值,即原始值和引用值. 原始值指的就是代表原始数据类型(基本数据类型)的值,即Undefined,Null,Number,String,Boolean类型所表示的值. 引用值指的就是复合数据类型的值,即Object,Function,Array,以及自定义对象,等等 栈和堆 与原始值与引用值对应存在两种结构的内存即栈和堆 栈是一种后进先出的数据结构,在javascript中可以通过Array来模拟栈的行为 复制代码 代码如下: va

RAMCloud:内存云存储的内存分配机制

现在全闪存阵列已经见怪不怪了,EMC的XtremIO,还有VNX- F(Rockies),IBM FlashSystem.全闪存真正为效率而生,重新定义存储速度.凭借极致性能,高可用性,为您极大提高企业级应用效率.提到闪存的优势,那么毋庸置疑的就是速度!而在速度优势背后,SSD则面临着价格.容量以及寿命等方面的限制. 当然随着技术的发展,成本的下降,SSD有可能会取代机械硬盘,成为下一代企业存储的主要介质.机械硬盘可能转变为磁带的角色. 但是,闪存速度的确就是现在存储系统的极限吗?现在有需要基于

C++ QVector 类介绍及内存分配策略

QVector 介绍 QVector类是一个提供动态数组的模板类. QVector<T>是Qt普通容器类的一种.它将自己的每一个对象存储在连续的内存中,可以使用索引号来快速访问它们.QList<T>.QLinkedList<T>和 QVarLengthArray<T>也提供了相似的功能,它们使用方法如下: l QList一般用得最多,它能满足我们绝大部分需求.像prepend()和insert()这样的操作通常比QVector要快些,这是由于QList存储它

解析操作系统的内存分配(malloc)对齐策略

问题: 我们在写程序的时候经常发现程序使用的内存往往比我们申请的多,为了优化程序的内存占用,搅尽脑汁想要优化内存占用,可是发现自己的代码也无从优化了,怎么办?现在我们把我们的焦点放到malloc上,毕竟我们向系统申请的内存都是通过它完成了,不了解他,也就不能彻底的优化内存占用. 来个小例子 //g++ -o malloc_addr_vec mallc_addr_vec.cpp 编译 #include<iostream> using namespace std; int main(int arg

c语言-C语言内存分配malloc导致的程序退出

问题描述 C语言内存分配malloc导致的程序退出 char *p; while (1) { p = malloc(1); *p = 0; } 这样写最后是因为没有内存退出还是向0写入退出?怎么感觉是内存完了 解决方案 内存分配失败就会返回空指针 解决方案二: 堆内存被使用完后,在申请就睡失败,p就是NULL,即地址为0写入,而这个地址是受程序保护的,无法写入因此退出 解决方案三: 个人觉得你这个应该是会导致堆内存不够,导致程序异常退出

malloc 内存分配的问题,求解答

问题描述 malloc 内存分配的问题,求解答 如上图,我定义了一个结构体,并且初始化(其中sock成员之后在其他地方初始化的的),为什么上课时老师老是说我这段内存分配有问题呢? 解决方案 my_struct的大小不能这么计算. 如果你想表达data是可变长度的,要么你将data设置为结构,要么设置为一个最大的可能值作为上限. 解决方案二: 你的data没有分配空间,所以你的memcpy()函数向data写数据不crash才怪. 解决方案三: 你的data是一个长度为0的字符数组,memcpy的

malloc,colloc,realloc内存分配,动态库,静态库的生成与调用

 1.在main方法里面直接定义一个非常大的数组的时候,可能会出现栈溢出:错误代码演示: #include<stdio.h> #include<stdlib.h> void main() {     int a[1024 * 1024];     int num = 100;     system("pause"); } 错误截图: 2.在定义数组的时候要定义数组的长度,否则会出现错误.(特例:在GCC编译器下,不会出现错误(因为标准不一样)). 3.打印并

关于c语言内存分配,malloc,free,和段错误,内存泄露

1.   C语言的函数malloc和free  (1) 函数malloc和free在头文件<stdlib.h>中的原型及参数        void * malloc(size_t size) 动态配置内存,大小有size决定,返回值成功时为任意类型指针,失败时为NULL.        void  free(void *ptr) 释放动态申请的内存空间,调用free()后ptr所指向的内存空间被收回,如果ptr指向未知地方或者指向的空间已被收回,则会发生不可预知的错误,如果ptr为NULL,