首页 > 学院 > 开发设计 > 正文

动态内存管理(new/delete)

2019-11-06 07:51:02
字体:
来源:转载
供稿:网友

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

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

内存的分配方式:①从静态存储区域分配。内存在程序编译的时候就已经分配好了,这些内存在程序整个运行期间都存在,如:全局变量、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__)#elsevoid 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()来进行存储管理。           


发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表