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

posted @ 2018-11-13 13:22  灰太狼锅锅  阅读(916)  评论(0编辑  收藏  举报