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堆内存
假设从《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,所以用户使用的堆地址不连续)。
由于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()后一定要判断堆内存是否成功。若对内存分配未成功使用指针操作内存也会使程序出现异常。动态分配内存时要采取以下结构:
- char *pL =NULL;
- ……
- pL = (char *)malloc( sizeof(char) * size);
- if(pL)
- {
- …
- }
分配成功后,得到的堆内存首地址一定要保存,不然后来无法释放堆内存而造成内存泄露。而且不可使用未初始化的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)。释放堆内存块采取这样的程序结构:
- if(pL)
- {
- free(pL);
- pL = NULL;
- }
6 指针赋值为NULL的道理
有笔记“C中的void和NULL”表面引用NULL指针的后果。为了更好的利用指针,避免野指针(指针所指的内存块不可用)的使用在所有使用指向堆内存块的指针前都采取如此的结构:
- if(p)
- {
- //通过指针操作堆内存
- ……
- }
定义指针后将其值赋值为NULL。此时指针指向的内存地址为NULL,NULL对指针的赋值是将指针置成空指针(什么也没有指向)还是将指针指向了一段特殊的地址取决于编译器,编程中我们不需要了解NULL到底代表什么,只需要用NULL来避免指针带来的后果。
定义指针后将其赋值为NULL之后的好处在于避免系统给予局部指针变量的随机值,我们在使用指针前(malloc()除外)都判断一下指针的值是否为NULL,只有在不为空的情况下才能对此进行操作,如free(p),若在不判断p是否为空的情况下进行free(p)操作则会造成内存泄露。
7 在含指针参数的函数内使用断言
(1)用断言判断指针是否为NULL
判断指针是否为NULL的主要针对对象是指向堆内存的指针。比如在以下内存拷贝函数中:
- flag my_strcpy(char *StrTo, char *StrFrom)
- {
- if(!StrTo || !StrFrom)
- {
- return -1;
- }
- char *StrToL, *StrFromL;
- StrToL = StrTo;
- StrFromL = StrFrom;
- while(*StrToL++ = * StrFromL++)
- NULL;
- return 0;
- }
程序中首先判断两个地址是否为空。判断StrTo是为了了解StrTo是否指向一段空间。当然若StrTo指向一个常数,往后拷贝操作还得出错。
像这样带指针参数的子函数内都很有必要有这么一段判断指针是否为NULL的语句,故而可以将这样的代码写成函数来供大家使用,再考虑此代码段比较小可以用宏代替。这样的(带参数)宏可称为断言,因为当指针未空时就退出子函数(如assert())。
如以上一段判断子函数是否为空可以用如下宏代替,形成一个断言:
- #define MY_ASSERT(pStrTo, pStrFrom) if(!StrTo || !StrFrom) \
- { \
- return -1; \
- }
然后在每个程序中直接调用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的内存分配原理的更多相关文章
- 深入理解golang:内存分配原理
一.Linux系统内存 在说明golang内存分配之前,先了解下Linux系统内存相关的基础知识,有助于理解golang内存分配原理. 1.1 虚拟内存技术 在早期内存管理中,如果程序太大,超过了空闲 ...
- 深入Java核心 Java内存分配原理精讲
深入Java核心 Java内存分配原理精讲 栈.堆.常量池虽同属Java内存分配时操作的区域,但其适用范围和功用却大不相同.本文将深入Java核心,详细讲解Java内存分配方面的知识. Java内存分 ...
- JVM内存分配原理
堆栈常量池等内存分配原理详解 存储的方式: 寄存器 栈(stack) 堆(heap) 静态域 常量池 非RAM存储 JAVA寄存器 最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制. ...
- 【转】linux环境内存分配原理 malloc info
Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...
- linux环境内存分配原理 mallocinfo
Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和free是如何分配和释放内存?如何查看堆内内存的碎片情况?既然堆内内存brk和sbrk不能直接释放,为什么不全 ...
- linux环境内存分配原理 mallocinfo【转】
转自:http://www.cnblogs.com/dongzhiquan/p/5621906.html Linux的虚拟内存管理有几个关键概念: Linux 虚拟地址空间如何分布?malloc和fr ...
- TCMalloc 内存分配原理简析
一.TCMalloc TCMalloc简介 为啥要介绍 TCMalloc? 因为golang的内存分配算法绝大部分都是来自 TCMalloc,golang只改动了其中的一小部分.所以要理解golang ...
- java内存分配原理
一般Java在内存分配时会涉及到以下区域: ◆寄存器:我们在程序中无法控制 ◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中 ◆堆:存放用new产生的数据 ◆静态域:存放在 ...
- JAVA中堆栈和内存分配原理
1.栈.堆 1.寄存器:最快的存储区, 由编译器根据需求进行分配,我们在程序中无法控制.2. 栈:存放基本类型的变量数据和对象的引用,但对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量 ...
随机推荐
- [Vue warn]: Error in render: "TypeError: Cannot read property '0' of undefined、vuejs路由使用的问题Error in render function
1.[Vue warn]: Error in render: "TypeError: Cannot read property '0' of undefined 注意,只要出现Error i ...
- ACM:图的BFS,走迷宫
题目: 一个网格迷宫由n行m列的单元格组成,每一个单元格要么是空地(用1表示),要么是障碍物(用0来表示).你的任务是找一条从起点到终点的最短移动序列,当中UDLR分别表示往上.下.左.右移动到相邻单 ...
- 交叉编译git
git依赖openssl.zlib. 首先编译openssl ./Configure linux-armv4 shared 修改Makefile,CC.RANLIB.MAKEDEPPROG为对应的交叉 ...
- Android基础新手教程——1.6 .9(九妹)图片怎么玩
Android基础新手教程--1.6 .9(九妹)图片怎么玩 标签(空格分隔): Android基础新手教程 1.本节引言: 可能有的一些疑问: 1.什么是.9图片? 答:图片后缀名前有.9的图片,如 ...
- ZOJ 2319 Beautiful People
LIS.先按S降序升序再按B降序排序(如果B不按降序排序的话就会覆盖掉正解),然后再对B用O(nlog(n))的LIS求解就可以了.用d数组标记每个元素在上升序列中的位置,然后根据d倒着找id就可以了 ...
- linux kernel下输入输出console怎样实现
近期工作在调试usb虚拟串口,让其作为kernel启动的调试串口,以及user空间的输入输出控制台. 利用这个机会,学习下printk怎样选择往哪个console输出以及user空间下控制台怎样选择. ...
- SecureCRT 命令行备注
> 查出某个域名绑定的IP nslookup api.kaixin001.com Non-authoritative answer: Name: a.kaixin001.com Addre ...
- vue 笔记二
vue制作weibo 交互 vue-> 1.0 vue-resource ajax php 服务器环境(node) this.$http.get()/post()/jsonp() this.$h ...
- JSONP简单例子
jsonp的原理很简单,主要利用了HTML中所有有src的属性的标签可以跨域的特点,利用script的src进行get请求,后端输出一段js代码的字符串在script中便会执行. 当然后端输出普通的j ...
- 快捷键jdeveloper
alt+home:定位文件ctrl+alt+space:代码自动提示alt+enter:自动导包ctrl+x:删除ctrl+-:类搜索ctrl+=:弹出当前打开列表ctrl+shift+back:最后 ...