C++的动态内存管理方式
在C语言中,我们使用malloc/calloc/realloc和free动态开辟和释放内存,而在C++中,我们则经常使用的是new和delete进行动态内存管理。
区别:
- malloc/calloc/realloc按字节开辟内存的;new开辟内存时需要指定类型,例如:
new int[10]
。 - new不仅可以做内存开辟,还可以做内存的初始化操作。
- malloc/calloc/realloc开辟内存失败,是通过返回值和nullptr作比较;而new开辟内存失败是通过抛出bad_alloc类型的异常来判断。
- malloc/calloc/realloc和free,都是c的库函数,new和delete都是运算符。
- 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和 delete[]。
- 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc/calloc/realloc与free不会。
new/delete操作内置类型
int main()
{
// 动态申请一个int类型的空间
int* ptr4 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr5 = new int(10);
// 动态申请10个int类型的空间
int* ptr6 = new int[3];
delete ptr4;
delete ptr5;
delete[] ptr6;
return 0;
}
new和delete操作自定义类型
class Test
{
public:
Test()
: _data(0)
{
cout << "Test():" << this << endl;
}
~Test()
{
cout << "~Test():" << this << endl;
}
private:
int _data;
};
void Test2()
{
// 申请单个Test类型的空间
Test* p1 = (Test*)malloc(sizeof(Test));
free(p1);
// 申请10个Test类型的空间
Test* p2 = (Test*)malloc(sizeof(Test) * 10);
free(p2);
}
operator new和operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。我们来看这样的一段代码:
int main()
{
int *p=new int;//开辟单个空间
int *q=new int[5];//开辟连续空间
delete p;//释放单个空间
delete[]q;//释放连续空间
}
其汇编代码为:
我们在这里可以清晰的看到,实际上new/new[]和delete/delete[]在底层调用的其重载函数operator new/operator new[]和operator delete/operator delete[]。
自己实现new/delete重载函数
operator new以及operator new[]函数底层实际也是通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果改应对措施用户设置了,则继续申请,否则抛异常。
//先调用operator new开辟内存空间,然后调用对象的构造函数(初始化)
void* operator new(size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
return p;
}
//先调用operator new[]开辟内存空间,然后调用对象的构造函数(初始化)
void* operator new[](size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
return p;
}
operator delete和operator delete[]函数最终是通过free来释放空间的。
//delete p;调用p指向对象的析构函数,再调用operator delete释放内存空间
void operator delete(void* p)
{
cout << "operator delete addr:" << p << endl;
free(p);
}
//delete p[];调用p指向对象的析构函数,再调用operator delete[]释放内存空间
void operator delete[](void* p)
{
free(p);
}
一般情况下我们都是使用系统所提供new和delete重载函数,只有当我们需要全面接管内存管理时,才会去自己实现这两个重载函数。
new/delete与new[]/delete[]的混用问题
建议大家在使用new/delete和new[]/delete[]时,尽量匹配使用,否则容易造成程序崩溃却又难以发现问题所在的问题,此处仅是为了加深对new/delete和new[]/delete[]的深度理解。
我们来看下面的代码:
void* operator new(size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new addr:" << p << endl;
return p;
}
void operator delete(void* p)
{
cout << "operator delete addr:" << p << endl;
free(p);
}
void* operator new[](size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new[] addr:" << p << endl;
return p;
}
void operator delete[](void* p)
{
cout << "operator delete[] addr:" << p << endl;
free(p);
}
int main()
{
int* p = new int[5];//开辟连续变量
delete p;//释放
int* q = new int; //开辟单个空间
delete[]q;
return 0;
}
其输出结果为:
我们可以得出结论,对于普通的编译器内置类型,new/delete[] new[]/delete可以混用,因为没有构造和析构的调用
void* operator new(size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new addr:" << p << endl;
return p;
}
//delete p;调用p指向对象的析构函数,再调用operator delete释放内存空间
void operator delete(void* p)
{
cout << "operator delete addr:" << p << endl;
free(p);
}
void* operator new[](size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new[] addr:" << p << endl;
return p;
}
//delete p;调用p指向对象的析构函数,再调用operator delete释放内存空间
void operator delete[](void* p)
{
cout << "operator delete[] addr:" << p << endl;
free(p);
}
class Test
{
public:
Test()
{
cout << "Test" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
};
int main()
{
Test* p1 = new Test();
delete[] p1;
Test* p2 = new Test[5];
delete p2;
return 0;
}
其结果为:
程序已经崩溃了。所以,我们可以得出结论,对于自定义类型的new和delete操作,不能够混用,只能相匹配着使用。
对于自定义类型混用new和delete程序崩溃的原因:
自定义的类类型,有析构函数,为了调用正确的析构函数,那么开辟对象数组的时候,会多开辟4个字节,记录对象的个数,而在反馈给指针存储对象的起始地址时会在内存开辟的起始地址上增加4个字节。
例如:
void operator delete(void* p)
{
cout << "operator delete addr:" << p << endl;
free(p);
}
void* operator new[](size_t size)
{
void* p = malloc(size);
if (p == nullptr)
throw bad_alloc();
cout << "operator new[] addr:" << p << endl;
return p;
}
//delete p;调用p指向对象的析构函数,再调用operator delete释放内存空间
void operator delete[](void* p)
{
cout << "operator delete[] addr:" << p << endl;
free(p);
}
class Test
{
public:
Test(){ cout << "Test" << endl; }
~Test(){ cout << "~Test()" << endl; }
private:
int ma;
};
int main()
{
Test* p2 = new Test[5];
cout << "p2 addr:" << p2 << endl;
delete[]p2;
return 0;
}
其结果为:
我们以画图为例:
而如果我们使用delete 去释放自定义类型使用new[]开辟的空间时,调用析构函数错误,其并不知道多开辟了四个字节,,进而导致空间释放失败,程序崩溃。