拖了这么久,终于有时间把这块的东西总结一下,已经大三下学期了,留给我的时间已经不多了,希望可以抓住大学时光的尾巴,不负父母的殷切期望!
首先让我们来看看内存的分配方式都有哪些?
内存的分配方式:
①从静态存储区域分配。
内存在程序编译的时候就已经分配好了,这些内存在程序整个运行期间都存在,如:全局变量、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()来进行存储管理。