C++之内存管理
C中的内存管理
malloc
calloc
realloc
上述三种方式是用户在堆上开辟空间的办法,用完后必须用free释放,否则会造成内存泄露。
值得一提的是实际开辟的内存比申请的空间大:前面多申请32个字节,后面多申请4个字节。具体原因这里就不再阐述,详情见C语言动态分配内存
C++中的内存管理
C语言内存管理方式在C++中可以继续使用,同时C++又提出了自己的内
存管理方式:C++中通过new和delete运算符进行动态内存管理。
new和delete、new[]和delete[]一定匹配使用
1. new 申请内存
new的用法:
new 数据类型 ; //申请内存空间。
new 数据类型 (初值); //申请内存空间时,并指定该数据类型的初值。
new 数据类型 [内存单元个数]; //申请多个内存空间。
new的样例:
int *p1 = new int; //申请一个大小为int的空间给p1
int *p2 = new int(2); //申请一个大小为int的空间给p2,并且初始化为2
int *p3 = new int[3]; //开辟一个大小为3个整型的连续空间给p3
源码剖析:
在调试过程中查看源码:
// new int
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);
}
// new int(3)
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);
}
// new int[3]
void *__CRTDECL operator new[](size_t count) _THROW1(std::bad_alloc)
{ // try to allocate count bytes for an array
return (operator new(count));
}
可以看到,无论是哪种方式使用new来申请空间,实际上都是调用malloc函数。如果调用malloc函数申请空间失败,就会调用_callnewh(size)在堆上做出处理,然后继续尝试malloc。如果做处理时失败,就会抛出异常。
因此,使用new来开辟空间时,也就不需要用户判断是否成功开辟了空间。如果调用new返回地址了,那么这段地址的空间一定是能用的;如果开辟空间失败,则会直接报错。
对于类:
new做的事:
1.调用operator new分配空间
2.调用构造函数初始化空间
new[N]做的事:
1.调用operator new分配空间
2.调用N次构造函数分别初始化每个对象
2. delete 释放内存
detele的用法:
detete 变量名; //释放单个数据类型内存
delete [] 变量名; //释放数组空间
detele的使用样例:
delete p1; //释放p1
delete p2; //释放p2
delete[] p3; //释放连续数组空间
源码剖析:
C中的free:
extern "C" _CRTIMP void __cdecl free(
void * pUserData
)
{
_free_dbg(pUserData, _NORMAL_BLOCK);
}
C++中delete:
// delete p1
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;
}
// delete p2
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;
}
//delete[] p3
void operator delete[]( void * p )
{
RTCCALLBACK(_RTC_Free_hook, (p, 0))
operator delete(p);
}
同样,可以看到delete实际上就是对free函数做了封装。delete p;只是释放*p中的内容,而不改变指针p本身,p存放的依然是申请空间时的地址。
对于类:
delete做的事:
1.调用析构函数清理对象
2.调用operator delete释放空间
delete []做的事:
1.调用N次析构函数清理对象
2.调用operator delete释放空间
3. 定位new表达式
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象,此不再从堆中申请新的内存。
//例子:
char * buffer = new char [500];
TestData *data1 = new (buffer) TestData; //在new上已有内存上构建对象
TestData *data2 = new (data1 + 1) TestData; //再次利用new在data1
//之后构建第二个TestData对象
定位new不同与常规的new运算符,定位new运算符不需要相应的delete运算符来释放内存。因为它本身就不开辟新的内存。
简单来说就是new运算符只是返回传递给它的地址,并将其强制转换为void *
,以便能够赋给任何指针类型。
用将定位new运算符来创建新的类对象后,当该对象消亡时,程序并不会自动地调用其析构函数,所以必须显示地调用析构函数。这个少数的需要显示调用析构函数的情况之一。
对于使用定位new运算符创建的对象,应以与创建顺序相反的顺序进行删除。原因在于,晚创建的对象可能依赖于早创建的对象。另外,仅当所有对象都被销毁后,才能释放用于储存这些对象的缓冲区。