在 C 语言中,我们一般使用 malloc
与 free
来申请与释放动态内存;而在 C++
中,一般建议使用 new
与 delete
来申请与释放堆内存。
从功能上来讲,new
与 delete
只是多了一个类的自动构造与析构的过程,其他功能基本相同。
一个简单的使用 new
与 delete
的程序源码如下。
int main() {
int *p = new int(1);
delete p;
return 0;
}
编译成汇编(gcc -S
)。
call __main
movl $4, %ecx
call _Znwm
movq %rax, -8(%rbp)
movq -8(%rbp), %rax
movl $1, (%rax)
movq -8(%rbp), %rax
testq %rax, %rax
movl $4, %edx
movq %rax, %rcx
call _ZdlPvm
可见 new
与 delete
调用了两个函数:_Znwm
与 _ZdlPvm
。
他们也可称作 new
与 delete
操作符函数。
new
与 delete
操作符函数new
与 delete
操作的操作符函数,简化下来如下。
void* operator new(size_t size) {
return malloc(size);
}
void* operator new[](size_t size) {
return malloc(size);
}
void operator delete(void *p) {
free(p);
}
void operator delete[](void *p) {
free(p);
}
在真正的实现中,情况会稍微复杂一点点。
void* _CRTDECL operator new(size_t size) _THROW1(_STD bad alloc) {
void *p;
while ((p = malloc(size)) == 0 ) {
if (_callnewh(size) == 0) {
// 无内存,抛出 bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
}
return p;
}
void operator delete(void *pUserData) {
_CrtMemBlockHeader *pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL) return;
_mlock(_HEAP_LOCK); // 对堆内存操作进行线程安全保护
__TRY
pHead = pHdr(pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse)); // 验证 block 类型
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return;
}
new
与 delete
操作符无法重载,但他们的操作符函数是可以重载的。
重载的格式如下。
// 全局操作符函数重载
void* operator new(size_t size [, param1, param2, ...]);
void operator delete(void *p, [, param1, param2, ...]);
// 类内部操作符重载
class CA {
public:
void* operator new(size_t size [, param1, param2, ...]);
void operator delete(void *p [, param1, param2, ...]);
};
new
与 delete
操作符的真正实现对于基本数据类型,new
操作符直接申请对应大小的堆内存,并返回地址给对应指针;
而对于复杂的类对象,new
操作符会先申请对应大小的堆内存,然后调用对象的默认构造函数,最后返回地址给对应指针。
delete
操作符直接 free
对应地址即可。
对于数组的 new
与 delete
,他们会多一道工序。
编译器会多申请一点内存来存储数组的长度,并且存储数组长度的内存置于最前面。
然后根据数组的长度对每个对象进行 new
与 delete
。
new
一个数组,如 int *p = new int[3]
。
编译器会帮忙申请一个 unsigned long
的内存,内存分布如下。
unsigned long (size)
int (p[0])
int (p[1])
int (p[2])
delete
对应数组时,会先根据 size
将所有申请的数组 free
,然后将 size
的内存释放。
for (int i = 0; i < size; ++i)
free(&p[i]);
free((unsigned int *)p - 1);
为什么要一个一个 free
?
因为对于对象来说,在释放内存前,每个对象还需要析构。