malloc vs new && delete vs delete[]
1.malloc vs new
①malloc分配的内存位于堆上,new分配的内存位于‘自由存储区’,自由存储区是C++中一个抽象的概念,有别于堆。一般的g++编译器实现的new的调用过程如下:new operator->operator new->malloc
平时我们用的都是new operator,例如:int* p = new int,new operator会继续调用malloc(malloc不能被重载),所以一般情况下自由存储区就是在堆上的,但是operator new可以被重载,所以通过operator new分配的内存未必都在堆上,因此free store 和 heap还是有点区别
②malloc返回的指针类型是void*,需要手动强转为需要的类型,而new不需要如此,new的返回类型是类型安全的
③malloc分配内存失败会返回nullptr,而new则会直接抛出异常。另外new还可以通过set_new_handler设置分配失败时执行的逻辑,malloc则没有提供这样的使用方式
④在数组的内存分配上有所不同,malloc只管分配多大的字节,new []可以指定分配的是数组,并使用delete []释放
⑤new和delete会调用类的constructor和destructor,malloc则不会
注:operator new是C++中的函数,内部调用malloc,返回类型为void*,只负责分配内存,而new operator是在C++编译层由编译器实现的,首先调用operator new分配内存,然后调用placement new调用对象的构造,如下可能是其实现的简单版本:
new T; template<T> T* new_operator(args) { void* mem = ::operator new(sizeof(T)); T* obj; if(BasicTypeTraits<T>::IsBasicType == false){ obj = new (mem) T(args); //placement new } else { obj = mem; } return obj; }
其中args是类的构造函数的参数,可以不传,BaiscTypeTraits用来判断是否是基本类型,即C++ traits的应用,如果是基本类型无需调用构造函数,否则就要使用placement new调用对象的构造函数。
这是单个类的情况,如果是new[] 就再加一个循环就OK了。
delete operator与之类似,首先调用对象的析构:obj->~T();然后再调用operator delete:operator delete(mem);
另外operator new的简单实现如下:
void * operator new(std::size_t size) throw(std::bad_alloc) { if (size == 0) size = 1; void* p; while ((p = ::malloc(size)) == 0) { std::new_handler nh = std::get_new_handler(); if (nh) nh(); else throw std::bad_alloc(); } return p; }
其中get_new_handler()获取set_new_handler设定的函数指针,如果没有设置就返回空。有趣的是,如果设置了set_new_handler,且传入的函数没有退出的话,可以发现程序将是一个死循环,直到malloc成功后才能结束循环。
注:malloc分配的时候要指定分配内存的大小,利用free释放的时候却不需要指定大小,那么free是怎么知道该释放多大内存呢?在实现上,malloc分配的内存大小会比传入的值稍大,多余的内存用来存储分配的内存大小等额外的关于这段内存的信息(用户是无权访问的,应该是由操作系统管理),然后在free的时候找到这个内存的大小,就可以释放对应的内存了。另外因为delete内部实现也是调用free,因此delete也不用传入内存大小。
2.delete vs delete[]
在使用new/delete的时候,使用new分配内存要用delete释放,使用new[]分配要使用delete[]。那么二者的区别到底是什么呢?
delete的过程是:先调用对象的析构函数,然后再调用free释放这个对象,delete[]同理。
delete[]的出现主要是为了解决释放对象数组的时候调用其析构的问题。在使用new A[100]的时候显示的传入了创建对象的个数,因此很明确要调用多少次构造函数。但是在释放的时候delete[]还是没有传入对象的个数,那么其该如何知道调用多少次析构函数呢?
可能的方式是这样的:因为delete在内部会调用free,而free能拿到内存的大小,delete的参数是个指针,可以获取指针的类型,就可以获得这个类型的大小,用总大小除以这个类型的大小就可以拿到数组中对象的个数。但是这样的前提是free先调用,在调用free之前不能拿到内存大小,而free一旦调用整个数组的内存就被释放了,这时候还没调用对象的析构呢,就可能导致内存泄漏了。那delete[]到底是怎么处理的呢?
答案就是:在new A[100]的时候,这个数组长度100被存在了整个数组的前面size_t字节处(size_t根据平台不同可能是4或8),相当于new A[100]的时候分配的总内存是:malloc需要记录内存信息的空间+对象个数空间(size_t)+对象数组,然后返回地址是对象数组的首地址。在delete[]的时候先去拿到这个对象个数,再循环调用析构函数。前面说了malloc记录那个内存总大小用户无权访问,但是这个对象个数我们是可以拿到的,示例代码如下:
#include<iostream> using namespace std; class A{ public: A(){} ~A(){} int a; char b; }; int main(){ A* a = new A[6]; size_t* p = (size_t*)a; p--; cout << *(size_t*)p <<endl; delete[] a; //如果换成 delete a;程序可能崩溃 a = NULL; int* aa = new int[100]; delete aa; return 0; }
最终的输出就是6
如果delete[] a换成delete a,那么就只会调用一个对象的析构,然后就将数组的内存释放掉了,相当于其他5个对象的析构均没有调用,C++对这种情况的处理是未定义行为,运气好的话最多就是内存泄漏,运气不好程序就崩溃了,所以new[] delete[]必须成对的使用,而不能窜用。
另外,如果new[]的是一个基本类型的数组,比如int,那么就不会将其长度记录,因为基本类型没有析构函数,直接调用free释放即可,所以上面示例程序的最后delete aa,是可以成功释放的,但是为了良好的编程习惯还是要写delete []。
参考:
https://stackoverflow.com/questions/240212/what-is-the-difference-between-new-delete-and-malloc-free
https://stackoverflow.com/questions/1518711/how-does-free-know-how-much-to-free
https://stackoverflow.com/questions/1350819/c-free-store-vs-heap