动态内存管理(new/delete)

3/8/2017来源:ASP.NET技巧人气:1267

拖了这么久,终于有时间把这块的东西总结一下,已经大三下学期了,留给我的时间已经不多了,希望可以抓住大学时光的尾巴,不负父母的殷切期望!

首先让我们来看看内存的分配方式都有哪些?

内存的分配方式: ①从静态存储区域分配。 内存在程序编译的时候就已经分配好了,这些内存在程序整个运行期间都存在,如:全局变量、static变量。 ②在堆栈上分配。 在函数执行期间,函数内部变量(包括形参)的存储单元都创建在堆栈上,函数结束时这些存储单元自动释放(堆栈清退)。 堆栈内存分布运算内置于处理器的指令集中,效率很高,并且一般不存在失败的危险,但是分配的内存容量有限,可能会出现栈溢出的现象。 ③从堆或者自由存储空间上分配。程序在运行期间用malloc()或new申请任意数量的内存,程序员自己决定释放的时间(用free或delete)。 一般的原则是:如果使用堆栈存储或者静态存储就能满足要求,就不要使用动态存储,原因是在堆上动态分配内存需要很可观的额外开销。 首先需要明确这一点:

malloc/free、new/delete 、new[]/delete[]一定要匹配使用,否则可能会出现内存泄漏的问题。

好了,下面让我们正式进入c++中动态内存管理的机制: 1.new的三种的使用方式: ①plain new/delete 函数原型:

void *Operator new(std::size_t )throw(std::bad_alloc);
void operator delete(void *)throw();

其实就是我们使用的一般的new/delete。 举例说明它们的使用方法:
int *p1 =  new int;//开辟了一个int类型的空间(并没有初始化)
int *p2 = new int(1);//开辟了一个int类型的空间,并用1进行了初始化
int *p3 = new int[3];//开辟了具有3个int类型的空间,类似于数组(都是连续的)

delete p1;
delete p2;
delete[] p3;//注意匹配使用


2.nothrow new/delete


顾名思义,nothrow new就是不抛出异常的运算符new的形式,nothrow new在失败时会返回NULL,所以使用它时就不用设置异常处理器,
而是和malloc()函数一样,检查它的返回值是否为NULL。
函数原型如下:


void *operator new(std::size_t, const std::nothrow_t &)throw()
 
void operator delete(void *)throw();


3.placement new/delete (定位new)------>允许在已分配成功的内存空间调用构造函数初始化一个对象。 显然:placement new不用担心内存分配失败,因为它根本就不会分配内存,它所做的唯一一件事就是调用对象的构造函数。 函数原型如下:

void *_operator new(size_t, void *);
void _operator delete(void *, void *);

使用方法: new (place_address) type; new (place_address) type(initializer-list); //place_address必须是一个指针,指向已开辟好的空间。 //initializer-list是类型的初始化列表。

示例如下(使用宏函数实现new/delete的功能):

总结:(对于内置类型) 1.operator new/operator delete/operator new[]/operator delete[]和malloc/free的作用相同。 都是开辟/释放空间,不会调用构造函数和析构函数。 2.operator new和operator delete实际上只是malloc和free的一层封装。

对于自定义类型: 1.new/delete、new []/delete[]  动态开辟的类型若为自定义类型 1)new的实现过程: operator new--->malloc函数----->若开辟失败,则抛异常---->开辟成功,调构造函数。

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
        {       // try to allocate size bytes
        void *p;
        while ((p = malloc(size)) == 0)
                if (_callnewh(size) == 0)
                {       // report no memory
                static const std::bad_alloc nomem;
                _RAISE(nomem);
                }

        return (p);
        }

2)new [count] operator new[count]----->operator new(count)---->malloc()---->调count次构造函数
void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
    {   // try to allocate count bytes for an array
    return (operator new(count));
    }

3)delete 的实现过程:调析构函数--->operator delete()--->free()

void operator delete(
        void *pUserData
        )
{
        _CrtMemBlockHeader * pHead;

        RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));

        if (pUserData == NULL)
            return;

        _mlock(_HEAP_LOCK);  /* block other threads */
        __TRY

            /* get a pointer to memory block header */
            pHead = pHdr(pUserData);

             /* verify block type */
            _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));

            _free_dbg( pUserData, pHead->nBlockUse );

        __FINALLY
            _munlock(_HEAP_LOCK);  /* release other threads */
        __END_TRY_FINALLY

        return;
}



4)delete[]的实现过程(显式定义了析构函数时): 取出new[]时保存的count--->调count次析构函数---->operator delete[]------->operator delete()---->free()
void operator delete[]( void * p )
{
    RTCCALLBACK(_RTC_Free_hook, (p, 0))

    operator delete(p);
}



综上可知,因为内置类型没有构造函数和析构函数,所以new和delete的作用和malloc和free几乎没有差别,所以当用new开辟空间,而不管使用delete/delete[]/free都可以成功的释放空间,所以不会造成内存泄漏。



当动态开辟的的类型是自定义类型时: 举例说明new[](显式定义了析构函数)在开辟空间时会多开4个字节的空间。 new  Date[3]------>当析构函数被显式定义时,动态会多开辟4个字节(返回地址向上多开4个字节)----->保存的是需要调析构函数的次数。 当没有显式定义析构函数时:(不会多开辟4个字节的空间)

总结(思考): 程序会崩溃的原因:实质上就是因为new[]会多开辟的四个字节(当显式定义析构函数时),delete[]--->当析构函数被显式定义时,释放空间会从地址向上四个字节处开始释放,而当析构函数没有被显示定义时,就会从动态开辟返回的地址处释放空间,只要明白这个规则,就会知道什么时候程序会崩溃,而什么时候程序不会崩溃。

new/delete、new[]/delete[]的执行过程小结: 1)new-->operator new()--->malloc()---->调用构造函数。(如果开辟失败,则会抛异常)。 new的作用:①调用operator new开辟空间;②调用构造函数初始化对象; 2)delete---->调用析构函数--->operator delete----->free() delete的作用:①调用析构函数清理空间;②调用operator delete释放空间。 3)new Type[count]--->operator new[]--->operator new--->malloc----->调count次构造函数 new[]的作用:①调用operator new开辟空间;②调用N次构造函数初始化每个对象。 4)delete[]------>取count(析构函数被显式定义)----->(1.调count次析构函数  2.operator delete[]---->operator delete--->free()) delete []的作用:①调用N次析构函数清理空间;②调用operator delete释放空间。

扩展: 1.重载函数operator new 如果类内部定义了operator new,则当调用函数时,优先调用自己定义的operator new, 如果类内部没有定义,而在类外定义了普通函数operator new,编译器则会显示调用类外部的, 当程序没有定义operator new时,才会调用系统的。

示例如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Date
{
public :
     Date()
     {
     }
     ~Date()
     {

     }

     void* operator new(size_t size, const char* file, long line)
     {
          cout << "file = "<< file <<"line = "<< line << endl;
          return malloc(size);
     }
PRivate :
     int _data;
};

#if _DEBUG
#define new new(__FILE__,__LINE__)
#else
void Test1()
{
     Date* pt = new Date;
     delete pt;
}

int main()
{
     Test1();
     return 0;
}

#endif



2.用malloc和new定位符来实现new的功能;用free来实现delete的功能。
//用new定位符和malloc实现new的功能
void Test1()
{
     Date* pd = (Date*)malloc(sizeof(Date));
     if (pd == NULL)
     {
          return;
     }
     new(pd)Date;//调构造函数
     pd->~Date();//调用析构函数
     free(pd);
}



用malloc和new定位符来实现new[]的功能;用new定位符和free来实现delete[]的功能。

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;

class Date
{
public:
     Date()
     {
          cout << "Date()" << this << endl;
     }
     ~Date()
     {
          cout << "~Date()" << this <<endl;
     }
private:
     int _data;
};

void Delete(Date* p)
{
     int count = *((int*)p);
     for (int idx = count - 1; idx >= 0; idx--)
     {
          ((Date*)p + idx)->~Date();
     }
     free(p);
}
void Test1()
{
     Date* pd = (Date*)malloc(10 * sizeof(Date) + 4);
     *((int *)pd) = 10;
     int count = *((int *)pd);

     for (int idx = 0; idx < count; idx++)
     {
          new(pd + idx) Date;
     }
     Delete(pd);
}

int main()
{
     Test1();
     return 0;
}



最后,让我们一起回答两个问题:

1、c语言中malloc/free和c++中特有的new/delete有什么区别和联系? ①  它们都是动态管理内存的入口; ②  malloc() 和free() 是c++/c语言的标准库函数,new/delete是c++的运算符,它们都可用于申请和释放动态内存。 ③  malloc和free只是动态开辟内存空间,而new/delete除了可以完成malloc/free的工作,除此之外,new运算符还会自动调用构造函数,delete运算符会自动调用析构函数。 ④  malloc/free需要手动计算类型大小且返回值类型为void *,new/delete可以自己计算类型的大小,并会返回对应类型的指针。 2、既然new/delete的功能完全覆盖了malloc() /free() ,那么c++为什么不把malloc() 和free() 淘汰出局呢? ①c++程序需要经常调用c函数,而且在c++中会用malloc() /free() 来实现new/delete, 而且c程序只能使用malloc() 和free() 来管理动态内存。 ②new/delete更为安全。因为new可以动态计算要开辟多大的内存空间,而malloc() 却不能,new可以直接返回对应类型的指针,而malloc() 需要强制类型转换。 ③我们可以自定义类重载new/delete,而malloc() /free()却不可以被任何类重载。 ④malloc()/free()可以提供比new/delete更高的效率,因此某些STL实现版本的内存分配器会采用malloc()/free()来进行存储管理。