目录
C++内存分布

C++内存管理方式
new和delete
C语言的内存管理方法在C++中也可以使用,但是在一些地方是无法处理的而且使用起来会比较麻烦,所以C++就提出了自己的管理方法:new和delete
对内置类型的使用
cpp
int main()
{
int* a1 = new int;//动态申请一个int类型的空间
delete a1;//释放空间
int* a2 = new int(1);//动态申请一个int类型的空间,并初始化为1
delete a2;
int* a3 = new int[10];//动态申请十个int类型的空间
delete[] a3;
return 0;
}
对于连续申请的多个空间的初始化
cpp
int main()
{
int* a = new int[10]{1,2,3};//动态申请十个int类型的空间,初始化前三个,其他的自动赋值为0
delete[] a3;
return 0;
}

对自定义类型的使用
cpp
class A
{
public:
A(int a)
{
_a1 = _a2 = a;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A* a1 = new A;
delete a1;
A* a2 = new A(1);
delete a2;
A* a3 = new A[10]{1,2,3};
delete[] a3;
return 0;
}
与内置类型不同的是,new/delete对于自定义类型对象不仅会开辟/释放空间,还会调用构造函数和析构函数,初始化了几个就调用几次构造函数和对应的析构函数

cpp
class A
{
public:
A(int a)
{
_a1 = _a2 = a;
}
A(int a1 = 0, int a2 = 0)
{
cout << "A()" << endl;
_a1 = a1;
_a2 = a2;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A* a = new A[10]{{1,1},{2,2},{3,3}};
delete[] a;
return 0;
}
在这里a的实现要依靠默认构造函数/或对应有参构造函数,如果没有的话就会报错
原理
内置类型:
对于内置类型,new/delete与malloc/free是相同的,唯一不同的是,new在申请空间失败后会自动抛异常,而malloc要自己手动实现来验证是否申请失败
自定义类型:
new的原理其实就是operator new() + 构造函数
先调用operator new(),再调用构造函数
operator new()函数:进行空间申请
构造函数:初始化对象

而operator new()函数其实就是由malloc()函数组成的,但和malloc()不同的是,它不需要在申请空间后要自己检查是否申请成功,operator new()函数会自己判断,然后根据申请成功与否来进行抛异常(在这里我们就不细讲抛异常,会在后文在做详细讲解)

delete的原理是operator delete() + 析构函数
先进行析构,再调用operator delete()
析构函数:将对象指向资源释放
operator delete():销毁空间

而operator delete()函数的底层就是free()函数

至于new T[N]和delete[]的原理和new/delete是一样的,只不过要根据申请了多少个空间来进行对应次数的构造函数和析构函数的调用
所以new/delete在底层其实就是malloc/free,不过是在malloc和free的基础上进行了一些封装,加了一些功能
匹配问题
在new和delete的匹配问题上,内置类型和自定义类型是不相同的
内置类型:
cpp
int main()
{
int* a1 = new int;//动态申请一个int类型的空间
delete a1;//释放空间
int* a3 = new int[10];//动态申请十个int类型的空间
delete[] a3;
//delete a3;//这样也可以进行a3的释放,内置类型可以考虑不加[]
return 0;
}
自定义类型:
cpp
class A
{
public:
A(int a)
{
_a1 = _a2 = a;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1;
int _a2;
};
int main()
{
A* a1 = new A;
delete a1;
A* a2 = new A(1);
delete a2;
A* a3 = new A[10]{1,2,3};
delete[] a3;
//delete a3;//error
return 0;
}

之所以会导致这样的问题,是因为一个这样的机制:
对于new一个自定义类型对象,在new[N]时,不仅会给对象申请一份空间,还会对[]中的N申请一块空间,我们可以在Vs上进行验证:

我们也可以从这个对象的大小来判断,原本根据A这个类来判断a3的大小应该是80个字节,而这里却是84个字节,那多出来的4个字节就是为[]中的数字开辟的空间

意义:
那有人就会有疑惑,开辟这个空间的意义在哪?
1、析构函数调用次数
我们在前面知道new了几个空间就需要调用几次构造函数和几次析构函数,new当然知道要调用几次构造函数(因为[]里面写好了),当时operator却不知道要调用几次析构函数啊!
所以就要开辟一块空间给[]中的数字,来告诉[],然后在operator后面加上[]就可以让operator调用正确次数的析构函数
但这种情况下编译器还不会报错

2、错误位置释放
在外面delete这个对象开辟的空间时,如果不加[],那我们就是从对象的地址开始释放,而前面的空间就无法释放,这就是最严重的问题:从错误地址开始释放,这样就会导致编译器报错

注:不需要匹配的情况
当类中没显式实现析构函数的情况下,不加[]是可以正常运行的,因为不会调用析构函数,而在这情况下,也不会给[]中数字开辟空间,会直接从对象首地址开始释放
在这里就涉及到编译器的优化,因为没有显式实现析构函数,所以编译器在不需要释放什么资源的情况下就会优化掉自己的生成的默认析构函数
new/delete和malloc/free的区别
malloc/free和new/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。
不同的地方是:
malloc和free是函数,new和delete是操作符
malloc申请的空间不会初始化,new可以初始化
malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需
要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理释放