目录
4.operator new与operator delete函数
1.C/C++的内存分布
(1)栈 : 局部变量,指针变量,函数参数,返回值。
(2)堆 :通过内存函数申请的空间。
(3)数据段(静态区): 全局变量,静态全局变量,静态局部变量。
(4)代码段(常量区):常量字符串,可执行代码。
2.C语言中动态内存管理方式
这里请参考之前写的C语言中的动态内存管理。
3.C++内存管理方式
C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因此C++提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
3.1new/delete操作内置类型
#include <iostream>
using namespace std;
void Test()
{
//动态申请一个int类型的空间 未初始化时为随机值
int* ptr1 = new int;
// 动态申请一个int类型的空间并初始化为10
int* ptr2 = new int(10);
// 动态申请10个int类型的空间
int* ptr3 = new int[10] {0,1,2,3,4,5,6,7,8,9};
cout << *ptr1 << " " << *ptr2 << " " << endl;
for (int i = 0; i < 10; i++)
{
cout << ptr3[i] << " ";
}
//对申请的空间进行销毁
delete ptr1;
delete ptr2;
delete[] ptr3;
}
int main()
{
Test();
return 0;
}
注:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[],注意匹配起来使用。
3.2new和delete操作自定义类型
new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间还会调用构造函数和析构函数,内置类型是几乎是一样。
先定义一个类:
#include <iostream>
using namespace std;
class A
{
public:
A(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{
cout << "A(int a1 = 0, int a2 = 0)"<< endl;
}
A(const A& aa)
:_a1(aa._a1)
,_a2(aa._a2)
{
cout << "A(const A& aa)" << endl;
}
~A()
{
cout << "~A()"<< endl;
}
private:
int _a1;
int _a2;
};
对new和delete进行测试:
int main()
{
// malloc和free不会调用构造和析构函数
A* p1 = (A*)malloc(sizeof(A));
free(p1);
//new和delete会调用构造和析构函数
A* p2 = new A;
A* p3 = new A(1);
delete p2;
delete p3;
}
int main()
{
A aa1(1, 1);
A aa2(2, 2);
A aa3(3, 3);
A* p4 = new A[3]{ aa1, aa2, aa3 };
A* p5 = new A[3]{ {1,1}, {2,2}, {3,3} };
A* p6 = new A[3]{ (1,1), (2,2), (3,3) }; //类型转换 + 拷贝构造->优化为直接构造
A* p7 = new A[3]{ A(1,1), A(2,2), A(3,3) }; //匿名
delete[] p4;
delete[] p5;
delete[] p6;
delete[] p7;
return 0;
}
创建aa1,aa2,aa3时调用3次构造函数,创建p4时调用3次拷贝构造,创建p5,p6,p7时各自调用3次构造函数,最后delete释放p4,p5,p6,p7进行12次析构,程序结束时自动释放aa1,aa2,aa3进行3次析构。其中对象拷贝时编译器对其的优化参考C++类和对象(4)中的对象拷贝时的编译器优化。
上述代码表明,用new创建数组对象时可以使用3中方式进行创建:
1.先创建多个对象,再用多个对象创建数组对象。
2.使用隐式类型转换的方式直接创建。
3.使用匿名对象的方式直接创建。对象拷贝时的编译器优化
int main()
{
//调用10次构造函数和析构函数
A* p9 = new A[10];
delete[] p9;
return 0;
}
3.3new失败会抛异常
#include <iostream>
using namespace std;
int main()
{
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
void* p3 = new char[1024 * 1024 * 1024];
cout << p3 << endl;
return 0;
}
这里只new出了第一个对象,并且程序崩溃了。
在C++中对需要申请资源的代码进行throw try/catch进行捕获异常。
#include <iostream>
using namespace std;
int main()
{
try
{
//throw try/catch
void* p1 = new char[1024 * 1024 * 1024];
cout << p1 << endl;
void* p2 = new char[1024 * 1024 * 1024];
cout << p2 << endl;
void* p3 = new char[1024 * 1024 * 1024];
cout << p3 << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
4.operator new与operator delete函数
new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过operator delete全局函数来释放空间。operator new 实际也是通过malloc来申请空间,如果malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
5.new和delete的实现原理
5.1内置类型
如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:new / delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申请空间失败时会抛异常,malloc会返回NULL。
5.2自定义类型
new的原理
1. 调用operator new函数申请空间。
2. 在申请的空间上执行构造函数,完成对象的构造。
delete的原理
1. 在空间上执行析构函数,完成对象中资源的清理工作。
2. 调用operator delete函数释放对象的空间。
new T[N]的原理
1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对象空间的申请。
2. 在申请的空间上执行N次构造函数。
delete[]的原理
1. 在释放的对象空间上执行N次析构函数,完成N个对象中资源的清理。
2. 调用operator delete[]释放空间,实际在operator delete[]中调用operator delete来释放空间。
5.3不匹配使用的情况
#include <iostream>
using namespace std;
class A
{
public:
A(int a1 = 0, int a2 = 0)
:_a1(a1)
,_a2(a2)
{
cout << "A(int a1 = 0, int a2 = 0)"<< endl;
}
A(const A& aa)
:_a1(aa._a1)
,_a2(aa._a2)
{
cout << "A(const A& aa)" << endl;
}
~A()
{
cout << "~A()"<< endl;
}
private:
int _a1;
int _a2;
};
class B
{
private:
int _b1 = 1;
int _b2 = 2;
};
int main()
{
//int* p1 = new int[10];
//delete p1; //对于内置类型不会有错
//B* p2 = new B[10]; //申请了80个字节,B没有写析构函数,编译器则优化了,所以只释放了80个字节
//delete p2; //对于没有构造和析构函数的类类型也不会有错
A* p3 = new A[10]; //申请了84个字节,开头多开4字节,存储元素的个数.给delete[]用,用delete释放空间不释放前面的四个字节
//delete p3; //对于有构造和析构函数的类类型new一个数组是用delete释放空间程序会崩溃
delete[] p3;
return 0;
}
6. 定位new表达式(placement-new)
定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
使用格式:
定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0)
: _a(a)
{
cout << "A():" << this << endl;
}
~A()
{
cout << "~A():" << this << endl;
}
private:
int _a;
};
int main()
{
//下面这两行代码是一样的,只是malloc失败返回NULL,operator new失败抛异常
A* p1 = (A*)malloc(sizeof(A));
A* p2 = (A*)operator new(sizeof(A)); //没有调用构造函数
//显式的调用构造函数初始化
new(p1)A(1); // 注意:如果A类的构造函数有参数时,此处需要传参
new(p2)A;
//显式调用析构函数
p1->~A();
p2->~A();
//下面两行代码也是一样的
free(p1);
operator delete(p2);
return 0;
}