C++内存管理的概念
C语言内存管理方式(malloc/free)在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
new/delete
new/delete操作内置类型:
int main()
{
int* p1 = (int*)malloc(sizeof(int));
//动态申请一个int对象
int* p2 = new int;
//动态申请一个int对象并初始化为1
int* p3 = new int(1);
//动态申请一个长度为4的数组并初始化为0
int* p4 = new int[4] {0};
free(p1);
delete p2;
delete p3;
delete [] p4;
return 0;
}
以下是关于代码的解释:
int* p1 = (int*)malloc(sizeof(int)):因为C++是C的延申,所以C++兼容C语言,在C++里也可以使用C语言的代码,malloc从堆中分配了一个int类型的空间,这个大家都熟悉.
int* p2 = new int;这里使用C++新引入的关键字new,new跟malloc操作类似,都是向申请了一个int类型的空间
int* p3 = new int(1);使用new向堆申请了一个int类型的空间,但不同的是在类型后面跟了(1),通过调试可以看到在申请完空间的同时将空间内的数据初始化为1。
int* p4 = new int[4] {0};使用new申请了一个长度为4的int数组,并将数组中的所有元素初始化为0。
free(p1);释放使用malloc分配的内存。
delete p2; delete同样也是C++新引入的关键字,用于释放动态分配内存的运算符,后面代码跟这段类似就不展开了。
new/delete操作自定义类型:
class A
{
public:
A(int a=0)
:_a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
A* p1 = (A*)malloc(sizeof(A));
cout <<"p1:"<< p1 << endl;
A* p2 = new A(1);
cout << "p2:" << p2 << endl;
free(p1);
delete p2;
return 0;
}
上图代码创建了两个对象指针,唯一区别的是p1指向的空间是通过malloc分配来的。p2指向的空间是通过new分配来的。
可以很明显的看出在通过new在堆上创建对象,会调用构造函数初始化对象,以及delete释放空间的时候会调用析构函数。
那么当通过new申请5个对象数组时候,通过上图代码会发现编译器为给每个对象都进行默认构造。如果将默认构造的参数删除变成普通的构造函数,那么再次运行程序编译则会报错。除非给每个对象都传入参数。
小结:
new/delete 和 malloc/free最大区别是 new/delete对于 自定义类型 除了开空间 还会调用构造函数和析构函数。
operator new与operator delete函数:
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是 系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过 operator delete全局函数来释放空间。operator new源码:
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
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
operator delete源码:
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;
}
free源码:
#define free(p) _free_dbg(p, _NORMAL_BLOCK)
通过上图operator new源码可以看见在operator new源码里同样调用了malloc,但是operator new会在使用完malloc后进行判断是否申请内存成功,如果不成功会抛出异常(显示异常状态) 。
operator delete与free同样都执行_free_dbg这段代码,但operator delete会做更多的处理,比如当重复释放同一块空间,operator delete则会抛出异常,让程序员更清楚的知道问题出现在哪。
上图代码写了一个try catch用于捕捉异常,以及do while的死循环,p1每次都向堆申请1MB的空间。通过任务管理器可以看到此时vs的运行窗口直接占用了2G左右的内存就不动了,说明此时堆上已经没有空间申请,当程序继续运行也没有发生而是捕捉到了编译器抛出的异常,输出bad allocation(糟糕的分配),提示程序员问题的出处。
小结:
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施 就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
new和delete的实现原理
实现内置类型:
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是: new/delete申请和释放的是单个元素的空间,new[ ]和delete[ ]申请的是连续空间,而且new在申 请空间失败时会抛异常,malloc会返回NULL。实现自定义类型:
上图通过new从堆上申请了一个A类的对象空间,通过p1指针进行接受,接着释放p1。下图将通过调试的反汇编可以很清晰明了的观察到new和delete是如何实现创建自定义类型的对象。
可以看到当使用new创建对象的时候并不是直接申请对象,而是先调用operator new从堆上申请一块空间,接着调用构造函数 A(int a),对对象进行初始化。
delete在一开始执行的时候会调用一个函数,这个函数封装了A类的析构函数以及operator delete。可以看到delete会先调用析构函数,将对象内的成员进行析构,再调用operator delete释放堆上对象占用的空间。
从上图例子并不能很好的看出new/delete的区别,接下来小编将实现一个栈的数据结构,让读者更好的理解为什么C++中会使用new/delete,而不是继续沿用malloc/free
上图实现了Stack类的数据结构,Stack类中的成员变量_arr是一个指针,在构造函数中向堆申请一块空间。在主函数里用new向堆中申请一块空间存放一个Stack对象,用st指针进行接收。new会先进行开空间,接着调用构造函数又给成员变量_arr申请了一块空间。当对st进行释放的时候,delete会先调用析构函数,释放_arr所申请的空间,再释放Stack对象的空间。