C++动态内存管理,malloc,calloc,realloc和new,delete的讲解

在C语言中,动态内存管理的函数是:malloc,calloc和realloc.使用结束后,需要我们通过free来进行手动清理。
malloc:按字节来开辟空间,如果开辟成功,就返回这段空间的首地址。否则,返回NULL。

函数原型是:void* malloc(size_t size);

calloc:在malloc的基础上,还可以进行初始化开辟出来的空间。

函数原型:void* calloc(size_t num,size_t size);

num:表示分配大小为size的空间的个数。
realloc:扩容。

函数原型:void *realloc( void *memblock, size_t size );

如果原来开辟的空间不够用,就可以用realloc来扩容。当原来开辟的空间不够用,但其加上连续的闲置空间够用时,realloc就会在原来的空间后面紧跟着开辟需要的空间大小,返回的仍然是原来空间的首地址;但是如果原来的空间的连续的闲置的空间不能满足需求时,ralloc就会在堆上的其他足够大地方重新开辟一段空间,然后返回新空间的首地址
在C++中,在C的基础上又有了新的内存管理的方式。使用new,delete;new[],delete[];operator new,operator delete;operator new[],operator delete[];
首先:new和delete是操作符,不是函数。operator new和operator delete与malloc特别相似。
下面我们来看看它们的用法:

    int* p = new int;
    *p = 13;
    cout<<*p<<endl;
    int* p1 = new int(8);
    cout<<*p1<<endl;
    int* p2 = new int[5];
    int i = 0;
    for(;i < 5;++i)
    {
        p2[i] = i;
        cout<<p2[i]<<"  ";
    }
    delete p;
    delete p1;
    delete[] p2;

这里写图片描述
从上述的例子中,我们可以看出,第二种是会直接初始化开辟出来的空间;第三种就是相当于开辟了一段空间。这样写是没有任何问题的。然后在释放空间时,用任何一种delete都可以。但是如果我们给类开辟一段空间呢?有时候就会存在问题。

#include<iostream>
using namespace std;
class AA
{
public:
    AA(size_t size = 5)
        :_size(size)
        ,_p(0)
    {
        cout<<"AA(size_t size = 5)"<<endl;
        if(_size > 0)
        {
            _p = new int[_size];
        }
    }
    ~AA()
    {
        if(_p)
        {
            cout<<"~AA()"<<endl;
            _size = 0;
            delete[] _p;
            _p = NULL;
        }
    }
private:
    int *_p;
    size_t _size;
};

int main()
{
    AA a(2);
    return 0;
}

这里写图片描述
当我们在类中定义一个数组,并未数组开足够的空间。在创建类的对象时,我们发现在使用new,delete时,不仅仅会开辟和清理空间,还会调用类的构造和析构函数
但是当我们new一个类时,同时也必须手动delete类。比如:

    AA* a = new AA;
    delete a;

这里写图片描述
在VS2010中我们进行单步调试,它的整体调用过程是这样的:这里写图片描述
最终,new还是调用了malloc函数。
那么,对应的delete也是同样的过程。这里写图片描述
从打印的结果可以看出,new了几个对象就会delete几次。
从VS2010中单步跟踪一下我们的程序:这里写图片描述
这里写图片描述
从汇编层我们可以理清new[]的实现过程:
这里写图片描述
当然,对应的delete[]也是相同的。
这里写图片描述
并且new,delete在开辟和清理空间同时,还调用了构造和析构函数。但是在初始化时调用构造函数是在new开辟空间结束后调用的,而析构函数时在对象的生命周期结束时先调用了析构函数,再调用了delete进行清理。
那么,如果我们在使用new和delete时没有匹配的使用,以为可以吗?

    int* k = new int;
    delete[] k;

    int* k2 = new int[10];
    delete k2;

在这样的情况下程序并没有发生任何错误。
但是当对一个类进行类似的不匹配的操作时:

    AA* a = new AA; 
    delete[] a;
    AA* b = new AA[10];
    delete b;

程序就会立马崩溃。我们分析一下过程:*

*严格的说,只要是由new[]来开辟空间,就一定会多开辟4个字节。但是,有时候并不是这样,类如上面的内置类型。怎么理解呢,就是当析构函数不可以调用时,就可以不多开辟4个字节。多开辟4个自己的目的就是为了记录调用析构的次数。

** 所以在使用的过程中一定要记住牢记new和delete要配对的使用,因为有些时候虽然我们的程序在没有配对的情况下可能恰好没有错误或没有崩溃,但我们也不知到底内存有没有泄露。
下面来整理一下malloc/free和new/delete的区别和联系:

  1. malloc/free是函数,而new/delete是操作符。
  2. malloc/free只是动态内存的开辟/释放空间,而new在开辟的同时也在调用构造函数,而delete在清理的同时也在调用析构函数。
  3. malloc/free在调用时需要我们手动计算大小且返回值是void*,而new/delete可以自己计算大小,且返回值是对应的类型。
  4. 当malloc空间开辟失败时,会返回NULL;而new开辟失败时则会抛异常。
    所以:new/delete更为方便。

猜你喜欢

转载自blog.csdn.net/yinghuhu333333/article/details/79762040