浅谈malloc-free and new-delete
对于每一个C++程序员来说,malloc/free和new/delete并不陌生,但是又有多少人能够真正理解了呢,我一直就不大理解,直到写这篇博文我仍然处于一知半解的状况,但总算解决了一些疑问。所以仅以此文记录下来,以解后来者之惑,同时供以后自己参考学习之用,水平有限,欢迎拍砖。
一. 问题提出
(1) malloc/free和 new/delete有何区别?
(2) 为何malloc只需传递一个size形参,却不需要内存数据类型?
(3) 为何free不需要传递size形参,而只需传递形参,且free形参类 型为void*那free是如何知道普通数据及数组大小的。
(4) 为何new/delete对普通数据和对数组的处理不同,数组为何加[]符号?且delete释放数组时也不需要对象个数?
二. 问题解释
(1) malloc/free 与 new/delete区别
区别在于malloc/free是C标准库函数,处理的是内置类型及结构体等数据的分配释放工作。而new/delete是C++内置操作符,它们不仅负责内置类型的分配释放,还处理对象类型的自定义类型的数据的分配和释放工作。它在对象分配时会调用构造函数,释放时会调用析构函数。很多实现中后者是对前者的封装,并且加入了类类型的处理机制,它只不过是比malloc/free做的工作多些罢了。
(2) malloc只需要传递一个size_t类型的参数,是因为malloc分配内存时并不关心数据类型,它是以字节为单位分配空间,把首地址作为void*返回,转换工作交给程序员来完成。其实在<<C和指针>>这本书中我们就已经充分体会void*的强大之处,<<STL源码剖析>>一书中也有体现。
(3) 而free不需要表示长度的型参似乎有点诡异,由于free接受的是void*类型的指针,所以它也没有可能以类型来获取长度,特别是如果free的目标是数组的话。要解释这个问题就不得不说明一个问题,那就是事实上malloc在分配内存是不仅仅分配所需空间大小,同时还会分配一些额外的空间以记录内存分配信息:
根据inside The C++ Object Model上说:现代C++编译器执行malloc函数时会记录分配空间的长度信息,实现机制一般有两种:
1) cookies:将一个记录内存分配大小的小块綁定在分配内存的地址头部(有些可能是尾部)
如:
int arr[10];
n arr[0] arr[1] arr[2] arr[3] ......
n表示字节数。
2) 空间分配表:系统会维护一个专门的内存分配表,在分配表中记录分配的内存的大小信息。
这里给出网上找到的一个vs2010作为测试环境,(猜测应该是win7 32位系统)下得出的内存分配公式:
对于普通数据:
公式(一):_CrtMemBlockHeader +<your data>+gap[N]
对于对象数组:
公式(二):_CrtMemBlockHeader +数组元素个数+<yourdata>+gap[N]
其中_CrtMemBlockHeader是用于存储内存分配信息的数据结构,其定义可能不同系统不同,一个例子为:
typedefstruct _CrtMemBlockHeader { // 指向前一块数据块的指针 struct _CrtMemBlockHeader *pBlockHeaderNext; // 指向下一块数据块的指针 struct _CrtMemBlockHeader *pBlockHeaderPrev;
// File name:请求内存分配操作的那行代码所在的文件的路径和名称,但实际上是空指针 char *szFileName; // Line number:行号,请求内存分配操作的那行代码的行号 int nLine;
// 请求分配的大小
// Type of block类型 int nBlockUse; // 请求号 long lRequest; // 这个数据是干嘛的呢,查下单词gap是什么意思,你就知道了! unsigned char gap[nNoMansLandSize]; } _CrtMemBlockHeader; |
gap[]是越界符,如0xFD。
至此我们解释了该问题,以此类推delete不需要大小的参数其原理大抵如此。
(4) 这个问题其实设计下面这个结论:
1) 对于单个数据必须使用delete
2) 对于普通数组(这些数组包括只含合成的析构函数的类数组-----trival deconstructor)用delete和delete[]都可以,是等价的。
3) 对于显示定义了析构函数的类数组必须使用delete[]
那么为什么会这样呢,那是因为对于有显示析构函数的类数组构造时其存储方式是公式(二)当你使delete时,它会把它当普通数据处理,那么它会把:
_CrtMemBlockHeader+数组元素个数
这9个字节的后8个字节当作_CrtMemBlockHeader来处理,那么读出的内存长度必然是错的,这样释放空间通过越界检查gap[]就可以发现内存释放操作错误,结果报错。
对于这个话题先讨论到这,进一步的研究等看完后续章节。
下面摘抄网上一段文章,我觉得能解释一些问题:
但因为new-delete申请和释放是类型相关的,所以其内部不仅关联到所申请内存的大小,还涉及到构造的类对象的个数。如代码new TYPE[num]。至于内存的大小我们已经明晰了,但是类对象的个数是否要记录呢,如果要记录,那存在哪里呢?可能现在有人会说不用记录啊,知道类对象的类型直接sizeof(TYPE),然后再用内存大小去除,不就可以了?答案是否定的。因为你申请的内存并不等于类对象的个数与类对象大小的乘积,它还存储额外的信息,如header,而且还要考虑到内存申请时的字节对齐。soso,无法通过内存大小计算,所以该类对象个数num是要记录的。也就是说new数组要记录两个数,这点是很关键的。那类对象个数num是记录在哪呢?是否也记录在内存里?no,不是,类对象的个数是有编译器编译过程中存储记录的(参考侯杰大师翻译的《effective c++》),而不是通过内存管理器。当你delete时,他会自动找打该指针对应的类对象的个数,循环调用析构函数,然后再free这块内存。