C++内存管理
C++管理动态内存的操作符有两组:
1.new/delete
new和delete的使用实例:
class Test
{
public:
Test()=default;
}
int main(void)
{
Test *t1 = new Test;//从堆申请一块Test大小的内存(自定义类型)
int *t2 = new int;//从堆上申请了一块int大小的内存(内置类型)
int *t3 = new int(1);//从堆上申请了一块int大小的内存并且初始化为1
//释放申请的内存
delete t1;
delete t2;
delete t3;
return 0;
}
2.new[]/delete[]
new[]和delete[]的使用实例:
//Test类同上
int main(void)
{
//申请了连续的10块大小为sizeof(Test)的内存
Test *t1 = new Test[10];
//释放掉new[]申请的内存
delete[] t1;
}
注意: C++中的动态内存管理操作符和向下兼容C语言的malloc/free函数一定要配套使用,否则就会在某种特定情况下出现内存泄漏,甚至程序会直接运行失败,不过缓慢的内存泄漏显然更加让人头疼!
那么什么时候会出现上面所说的问题呢?
class Test
{
public:
Test()
{
cout<<"Test()"<<endl;
}
~Test()
{
cout<<"~Test()"<<endl;
}
}
int main(void)
{
Test *t1 = new Test;
free(t1);
delete[]t1;
Test *t2 = new Test[10];
free(t2);
delete t2;
int *t3 = new int;
free(t3);
delete[]t3;
return 0;
}
代码解析:
1.用free释放t1指向的空间,程序不会运行失败,但是由于free不会调用对象的析构函数,所以如果对象内有申请其他的空间,而析构函数内包含对其他内存空间的释放,则程序就会出现内存泄漏。
2.用delete[]释放t1指向的空间,程序会运行失败,因为delete[]和delete的底层实现有区别,所以会出错。如果没有自定义析构函数,则不会出错,原因也是和delete[]的底层实现有关。
3.用free释放t2指向的空间,程序会运行失败,因为delete[]和free的底层实现有区别,所以会出错。
4.用delete释放t2指向的空间,程序也会运行失败,原因同3。
5.由于int是内置数据类型,所以即使不匹配程序也不会运行错误,但是可能会造成内存泄漏。
如果你仅仅想要知道C++怎样进行动态内存管理的,那么下面这段内容就可以略过不看,请直接翻到结尾。如果想要搞明白上面那些事实的原理,那么就得了解了解new/delete, new[]/delete[]的底层原理:
<———————————————————>
new/delete以及new[]/delete[]的底层原理
1.首先你得知道这两个C++中定义的函数:
operator new
operator delete
是的,这是两个函数,这两个函数和C语言中的malloc以及free没有什么区别,完全可以用malloc和free来替代这两个函数的“大部分”功能,可是既然是大部分,就说明还有区别:这两个函数会调用构造函数和析构函数。
2.底层的调用顺序:
new->operator new->malloc
delete->operator delete->free
3.动态内存分配的内幕
使用new[]的意思可以理解为申请 指定块 指定类型大小的空间,eg:
Test *t4 = new Class[10];
,
那么底层的delete[]在析构对象的时候,怎么知道要调用多少次析构函数呢?
原因就在于使用new[]申请的空间除了我们需要的数据空间外,在数据空间的前面还有一段空间(4字节)保存着一个计数,这个计数就是申请了多少块指定类型的空间。new返回的地址是数据空间的地址,而不是真正申请到空间的起始地址,而使用delete[]的时候,编译器就会自动向前寻址直到计数空间的开始地址,然后得到计数,根据这个计数来决定调用几次析构函数。
而new因为是申请一块空间,所以就不用多申请那块计数空间。而delete在释放空间的时候,自然也不会向前寻址去找计数空间。
现在,上面代码的问题理解起来就没有问题了。
因为内置类型使用的是默认的析构函数,而编译器在这里做了一个优化,只要检测到析构函数不用进行资源的释放,那么在使用new[]的时候就不会多申请4个字节的空间,自然delete[]也就不会向前寻址,所以即使不配套使用,也不会有问题。
如果自定义类型的数据使用的也是缺省的析构函数,那么效果也和内置类型相同。
如果自定义类型没有使用缺省的析构函数,那么由于动态内存操作符的底层特性,就会出现段错误(Segmentation fault)。
<——————————————————–>
new/delete和new[]/delete[]以及malloc/free的区别比较
注:为了方便描述,我就直接按照书写顺序按①,②,③来代替每组操作符/函数
①和③
1.前者除了申请和释放空间,还会调用构造函数和析构函数进行对象初始化和清理工作,而后者只是申请和释放空间
2.前者是C++操作符而后者是C/C++标准库函数
3.前者返回对应的数据类型指针,而后者返回的是`void*`类型指针
4.new申请空间的时候不用手动计算空间大小,而malloc需要
5.new申请空间失败会抛异常,而malloc函数申请空间失败会返回NULL
①和②
前者申请的空间只包含数据空间,释放也只释放数据空间。后者除了会申请数据空间,在自定义类型中有非缺省析构函数时,在数据空间的前面还有一段计数空间(4字节),释放的时候会同时释放掉计数空间和数据空间。