堆区的内存管理的成员必须要面对的问题?有申请有使用有释放。俗话说:有借有还再借不难。
在C中常用malloc动态申请堆区的内存空间,比如链表的存储,链式地址解决哈希冲突,邻接表,树等等一些数据结构用到的存储就够基本上都是堆区的内存,方便管理,用起来比较自由。而C++中我们常用new来开辟一块内存空间。
下来我们来看看new和malloc的区别:
malloc的全称是memory allocation,动态分配内存;
原型:external void *malloc(unsigned int byte);
申请byte大小的空间(空闲块表 free block list),成功返回该内幕才能块的首地址,错误则返回NULL;
用malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
(1)、malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
(2)、 malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
new和malloc的区别:
(1)new/delete是运算符;malloc、free是库函数
(2)C++中new/delete能调用构造函数和析构函数,而malloc/free不能,只是单纯的申请内存空间;(能否调用构造函数);
(3)malloc不会抛出异常,而new会;你无法重新定义malloc失败时的默认行为(返回null),但你可以重定义new失败时的默认行为,比如不让它抛出异常。--针对内存分配失败下面会接着讲。
(4)为什么不淘汰malloc/free,因为C++中经常调用C语言函数,只能使用malloc/free,malloc和delete使用就会出错;free无法执行析构函数;
以上的区别中有几个疑问需要解决一下:
问题一:malloc()到底如何申请内存空间?
malloc()到底从哪里得到了内存空间?答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。
malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表(Free List)。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块(根据不同的算法而定(将最先找到的不小于申请的大小内存块分配给请求者,将最合适申请大小的空闲内存分配给请求者,或者是分配最大的空闲块内存块)。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。
在此也要说明就是因为new和malloc需要符合大众的申请内存空间的要求,针对泛型提供的,分配内存设计到分配算法和查找,此外还要避免内存碎片,所以其效率比较低下,因此有时程序猿会自己重写new和delete,或者创建一个内存池来管理内存,提高程序运行的效率。
问题二:运算符和函数的区别是什么?
(1)运算符只能重载不能自定义,函数的名字随便你起,(new也是通过malloc实现的)只要是个标志符就行;但运算符不行,比如,你无法仿照其它语言的符号,自己定义一个乘方运算符“**”。
(2)任何函数都可以重载或者覆盖,但通常你不能改变运算符作用于内置类型的行为,比如你不能通过重载“operator+”,让3 + 2产生出6来。
(3)函数有函数的调用,依赖于库,但是运算符是语言固有的属性,如+-*/,编译器可以进行翻译;
问题三:有了new/delete和malloc/free的区别?
(1) malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
(2) 对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。。我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。
(3) 既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,虽然理论上程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。
(4)new自动计算需要分配的空间(因为new内置了sizeof),而malloc需要手工计算字节数;例如:int *p=new int[3]; int *pp=malloc(sizeof(int)*3);
问题四:C语言中的malloc/calloc/realloc/valloc/alloca/memalign函数内存分配失败后其处理方式:
因为这些函数如果内存申请发生错误,则都会返回NULL,所以将指针和NULL比较就可以了,非常简单。
例如:
例如:int *p=(int *)malloc(sizeof(int)*4); if(p==NULL) { //错误处理; } char *p1=(int *)calloc(3,1024); if(p1=NULL) { //错误处理; }
问题五:C++中new分配内存失败的处理方法:
C++中的new操作符在分配内存失败时默认的操作是抛出一个内置的异常,并不是和C中的malloc一样直接返回NULL,所以再把返回值与空指针比较久没有了意义,因为C++抛出异常之后,就直接跳出new操作符所在的那一行代码,不再后续执行了,所以对new操作符返回值的判断代码就执行不到了;当然标准的C++也提供了抑制抛出异常的方法,使之在内存分配出错时不再抛出异常而是直接返回NULL,这是因为古老的编译器里面可能没有异常处理机制,不能捕获到异常。
int *p=new int[10]; if(NULL==P) //这里的比较没有意义; { //错误处理; } 所以C++提供两种方法来处理new操作符分配内存失败的错误; (1)通过捕获new抛出来的异常; int *p=NULL; try { p=new int[10]; } cache(const std::bad_alloc &pp)//捕获异常; { //错误处理函数 } (2)抑制异常的抛出,直接返回NULL int *p=NULL; p=new(std::nothrow)int[10]; //这样的话,如果new分配内存失败,就不会再抛出异常,而是直接返回NULL了; if(NULL==P) //这里的比较就是有意义的; { //错误处理; }
注:使用free和delete后,一定要给指针赋空值或者指向一个合法的内存空间对象,避免野指针的出现;
释放数组对象或者申请的动态数组空间一定要加上[].例如:delete []p;
问题六:malloc/free和new/delete释放后的内存系统会马上回收吗?
理论上讲,free掉的内存肯定是要回收的,但是不一定会马上被OS回收,这是合理的。试想一下,你每次free掉的内存都还给OS的话,尤其是在小字节的情况下,那么造成的情况,就是一大块的内存被你弄的千疮百孔,也就是说一块内存,里面有很多gap。而在操作系统的虚拟内存管理中,更是管理着的是固定大小的内存,如4K,那你还给我1 Byte,OS显然是很尴尬的。于是为了避免这样的问题,那么内存管理一般会有一个free block list,free掉的东西就放在这里来。那么你可能会释放很散乱的内存过来,没关系,我们在这里会尝试合并这些散乱的block,而malloc首先找的也是free block list,而非从OS申请新的内存。那么此时如果找到了一块儿合适的自然最好,如果找到的是比要的更大,那么一部分malloc,另一部分放回去。而上面有同学提到了小内存的问题,而这也是free block list在头部会有一些所谓的administrative data,所以用标准的malloc和free管理小内存是不高效,因为越小越容易造成gap。当然,由于malloc和free是如此普遍,自然会尝试着让它变的更好,所以也有各种优化,如对free block list进行chunk size排序,什么情况下合并,什么时候分割,内存的分配算法等,都有很多机制来管理小内存。
问题七:malloc和free操作的都是物理内存吗?
malloc分配的永远是该进程的虚拟内存,malloc是调用VirtualAlloc的。你可以直接调用VirtualAlloc,仅仅保留虚拟地址空间而不提交,这样的话,不会分配物理内存,仅仅保留一段虚拟地址空间不被映射而已。等到需要的时候(需要写的时候)再提交物理内存,这时候得靠OS来分配相应的物理内存空间,有专门的MMU来执行操作。free当然也只是释放虚拟内存。物理内存的释放(也许不应该叫释放,而是物理内存页的重用),是换页算法做的。这块儿是专门由OS来负责管理的。
资料博文参考:
http://blog.csdn.net/zjc156m/article/details/16819357
http://www.cnblogs.com/huhuuu/archive/2013/11/19/3432371.html
http://bbs.csdn.net/topics/80028776
http://www.zhihu.com/question/29161424
在此感谢博主的分享!