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. }

分配成功后,得到的堆内存首地址一定要保存,不然后来无法释放堆内存而造成内存泄露。而且不可使用未初始化的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

malloc的内存分配原理相关推荐

  1. JAVA中堆栈和内存分配原理

    JAVA中堆栈和内存分配原理 1.栈.堆 1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. 2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在 ...

  2. 深入Java核心 Java内存分配原理精讲

    深入Java核心 Java内存分配原理精讲 Java内存分配与管理是Java的核心技术之一,之前我们曾介绍过Java的内存管理与内存泄露以及Java垃圾回收方面的知识,今天我们再次深入Java核心,详 ...

  3. 【转】linux环境内存分配原理 malloc info

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  4. linux环境内存分配原理

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  5. linux环境内存分配原理 mallocinfo

    Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...

  6. malloc的内存分配之 malloc(0)的内存分配情况

    #include<iostream> using namespace std; int main() {char *p;if((p=(char *)malloc(0))==NULL)put ...

  7. malloc动态内存分配

    运用动态内存分配可以有效解决定义的数组或者结构体大小不够用的情况,动态内存分配就是人为的向系统申请一个指定大小的空间用来存放临时数据.在c语言中,一些可控或者说是一些小数据下,可以人为定义一些变量用来 ...

  8. ESXi 内存分配原理

    上一篇我们详细讲述了CPU的调度原理,本篇讲一下内存的分配过程. 运行在ESXi主机上的虚拟机分配内存之和可以超过物理机的实际内存大小,这个技术叫做超额分配(overcommitment),即使单个虚 ...

  9. 使用malloc动态内存分配一个二维数组

    由于一部分编译器(比如说vs 2019)不支持创建数组时元素个数为变量,今天使用malloc来实现此功能. 详细注释和代码如下 //动态内存分配 模拟二维数组 #include<stdio.h& ...

最新文章

  1. 【高端】几个关于SCSS中for循环的高级玩法
  2. 【问题收录】Eclipse the import java.awt cannot be resolve 问题解决
  3. 宏基因组学习交流4群成立
  4. python导入模块快捷键_Python中的模块导入和读取键盘输入的方法
  5. Linux内核--网络协议栈深入分析(二)--sk_buff的操作函数
  6. 导航跳转后保持选中状态 jquery高亮当前选中菜单
  7. 牛客14392 猴子吃香蕉
  8. 太平鸟上云 推动中国服饰行业新零售转型
  9. MTK 驱动(47)---使用PWM配置背光如何配置,及频率计算
  10. Microsoft.Jet.Oledb.4.0 找不到提供者或未安裝問題
  11. IGS发布RINEX 4.00
  12. 阴阳师android转ios,阴阳师手游IOS自动刷御魂?IOS切换控制教程[多图]
  13. java跳转_java后端实现页面跳转的方法
  14. BDrate、BDBR、BDPSNR的计算原理和程序
  15. CDN和DNS的区别
  16. codeforces 1567 E. Non-Decreasing Dilemma
  17. 变量 内存 分配 ios iphone
  18. ES6的Array.from方法创建长度为N的undefined数组
  19. 北京汉正天成科技有限公司大家听说过没?这家公司怎么样?
  20. matlab数组下标可为正整数和逻辑数

热门文章

  1. Linux(三)——mysql服务
  2. 与安装应用签名不同怎么解决_天堂巴比伦 安卓游戏安装失败怎么解决
  3. 计算机一级单元格公式设置错误是什么原因,excel表格公式出错怎么解决,电子表格求和出错...
  4. 理解Windows内核模式与用户模式
  5. char[] 转换为LPWSTR
  6. clion opencv安装_Clion+Opencv3.2终极配置教程
  7. java例7_在Java 7中处理周数
  8. servlet post 返回值是一个对象_Servlet第二天
  9. 如何安装gnuplot
  10. Java基础day20