【C++】3-C/C++内存管理
1. C语言动态内存管理方式
malloc/calloc/realloc/free,这里不再细述。
int main()
{
// 动态申请4个整型大小的内存空间给ptr1
int* ptr1 = (int*)malloc(sizeof(int) * 4);// 数组中元素值为随机值
// 动态申请4个整型大小的内存空间给ptr2,并初始化为0
int* ptr2 = (int*)calloc(4, sizeof(int)); // 数组中元素值为0
// 对ptr2所指向的空间进行扩容,扩容到10个整型大小
// 若空间足够,则原地扩容,在ptr2所指向的空间后面紧接着再开辟6个整型大小的空间,ptr2 == ptr3
// 若空间不够,则异地扩容,在新位置重新开辟10个整型大小的空间给ptr3,并释放ptr2所指向的空间
int* ptr3 = (int*)realloc(ptr2, sizeof(int) * 99999999999999);// 原来的空间值不变,新扩容的空间值为随机值
return 0;
}
2. C++动态内存管理方式
C++使用new关键字动态申请空间,使用delete关键字释放动态申请的空间。
- 对于内置类型,new/delete与malloc/free相比,只是用法上有所区别罢了
int main()
{
// 内置类型
// 相比于malloc/free,new和delete只是用法上有区别
int* p1 = new int;// 默认不会初始化,是随机值
delete p1;
// 手动初始化
int* p2 = new int(0);// 初始化为0
delete p2;
// 开辟3个int空间,即数组
int* p3 = new int[3];// 默认不会初始化
delete[] p3;// 数组释放要加[]
// 初始化
int* p4 = new int[3]{ 1,2,3 };
delete[] p4;
return 0;
}
-
对于自定义类型,除了空间管理(开辟空间和释放空间)相同外,new会调用默认构造函数且delete会调用默认析构函数,而malloc和free则不会调用默认函数
class A { public: A(int a = 1) : _a(a) { cout << "A()" << " " << _a << endl; } ~A() { cout << "~A()" << endl; } private: int _a; }; int main() { // 自定义类型 A* p1 = (A*)malloc(sizeof(A)); free(p1); cout << "-----" << endl; A* p2 = new A;// 调用默认构造函数 delete p2;// 调用析构函数 return 0; } // 输出 ----- A() 1 ~A()
-
强烈建议new和delete要匹配。(即new单个类型空间,就用delete,new数组,就用delete[]。也不要new和delete与malloc和free混用)
int main() { // 抵制下列写法,可能报错,也可能不报错 // 一般情况,对于内置类型,正常运行;但对于自定义类型,可能会出现各种问题 // 具体的依赖于编译器的实现机制 // 抵制1 int* p1 = new int; delete[] p1; // 抵制2 int* p2 = new int[3]; delete p2; // 抵制3 int* p3 = new int; free(p3); // 抵制4 int* p4 = new int[3]; free(p4); // 抵制5 int* p5 = (int*)malloc(sizeof(int)); delete p5;// 或者:delete[] p5 // 抵制6 int* p6 = (int*)malloc(sizeof(int) * 3); delete[] p6;// 或者: delete p6 return 0; }
若(对自定义类型开辟/释放空间)不匹配,具体报错与否取决于编译器的底层实现机制。
(以下是vs2019编译器下执行)
-
new开辟空间失败,不会返回nullptr,而是会抛出一个异常(这里仅仅是提及)。
3. operator new和operator delete函数
- new和delete是用户进行动态内存申请和释放的操作符。
- operator new 和operator delete是系统提供的全局函数,非重载(没有自定义类型参数)
- new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。
- operator new底层是使用malloc申请空间,申请失败会抛异常(而非返回nullptr),operator delete底层是使用free释放空间
4. new和delete的底层实现
4.1 内置类型
对于内置类型,new和delete与malloc和free类似。只是:
- new和delete开辟/释放的是单个元素的空间
- new[]和delete[]开辟/释放的是一块连续的空间
- new申请空间失败,会抛出异常,不返回nullptr;malloc则会返回nullptr(C语言为NULL)
4.2 自定义类型
对于自定义类型,new和delete除了开辟/释放空间,还会调用默认构造/析构函数。
具体原理如下:
- new:
- 调用operator new函数动态申请单个元素的空间
- 若申请成功,调用默认构造函数对申请的空间初始化
- delete:
- 调用默认析构函数对已经申请的空间进行清理
- 调用operator delete函数释放空间
- new Type[N]:
- 调用operator new[]函数动态申请一块连续的空间(实际是在函数operator new[]中调用N次operator new函数)
- 若申请成功,调用N次默认构造函数对申请的连续空间的每一块单个元素的空间初始化
- delete[]:
- 调用N次默认析构函数对申请的连续空间的每一块单个元素的空间清理
- 调用operator delete[]函数释放空间(实际是在函数operator delete[]中调用N次operator delete函数)
5. 定位new表达式(placement-new)
功能:在已经开辟(分配)好的空间中显式调用构造函数初始化对象(即对已经实例化的对象显式调用构造函数,进行初始化)。
语法:new(place_address) Type
或 new(place_address) Type(initializer-list)
place_address是一个指针,Type是自定义类型,initializer-list是自定义类型的初始化列表
**使用场景:**一般结合内存池使用,因为从内存池中分配出的内存没有被初始化,所以如果从内存池中分配出一块空间给一个自定义类型对象,就需要显式调用构造函数对其初始化(这里不细述)
class A
{
public:
A(int a = 1, int b = 2)
: _a(a)
, _b(b)
{
cout << "A():" << this << " " << _a << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
int _b;
};
int main()
{
// malloc可以理解为也是一个内存池
A* p1 = (A*)malloc(sizeof(A));
// 显式调用构造函数初始化p1指向的空间
new(p1) A;// 不传参
new(p1) A(5);// 传参1
new(p1) A(10, 20);// 传参2
// 上面三种定位new方式用一种即可
p1->~A();// 析构函数可以直接显式调用
free(p1);
// 上面两行代码等价于:delete p1;
return 0;
}
// 显式调用构造函数初始化p1指向的空间
new(p1) A;// 不传参
new(p1) A(5);// 传参1
new(p1) A(10, 20);// 传参2
// 上面三种定位new方式用一种即可
p1->~A();// 析构函数可以直接显式调用
free(p1);
// 上面两行代码等价于:delete p1;
return 0;
}