动态内存管理
- 题外知识点
struct在c语言中,如果不进行typedef,则结构体的名字是struct A。
而在c++中,不用typedef,也可以直接使用A a;在c++中,struct可以当做是类,只是与class的区别是:如果在类class中不声明访问限定符,则默认为private,而在struct中,默认为public。
在c++中,内置类型也有构造函数。
int i = 0;
i = int();//i=0,i匿名对象
i = int(20);//i=20
- 在c语言中动态开辟空间
使用malloc、calloc、relloc、free等库函数。
- 在c++中动态开辟空间
可以使用malloc、calloc、relloc、free,但是c++有属于自己的开辟空间的操作符new和delete。
new/delete动态管理对象。
new[]/delete[]动态管理对象数组。
一定要匹配使用!一定要匹配使用!一定要匹配使用!
否则可能会出现内存泄漏等巨大的损失。
- 使用内置类型开辟空间。
void test1() {
//开辟空间
int* p1 = new int; //单指定为int类型,但是不初始化,还是随机值。
int* p2 = new int(10);//指定为int类型且初始化值为10。
int* p3 = new int[10];//指定为int类型,不是初始化,而是说明了申请的int的大小的空间为10个。
//释放空间
delete p1;
delete p2;
delete[] p3;
}
- 使用自定义类型开辟空间
class A {
public:
A(size_t size = 10)//构造函数
: _size(size)
, _a(0) {
cout << "A(size_t size)" << endl;
if (_size > 0) {//如果输入的size大于零,则开辟一段类型为int的size大小的空间
_a = new int[size];
}
}
~A() {//析构函数
cout << "~A()" << endl;
if (_a) {
delete[] _a;//释放a
_a = 0;
_size = 0;
}
}
private:
int*_a;
size_t _size;
};
void test2() {
A* p4 = new A;
A* p5 = new A(10);
A* p6 = new A[10];
delete p4;
delete p5;
delete[] p6;
}
- 比较
malloc和free只负责开辟空间。
new和delete不仅开辟空间,还会调用构造函数和析构函数。(建议在c++中尽量使用)
- new是先申请空间,后调用构造函数。
- 因为需要先申请到空间,才能在那个空间上构造对象。
delete是先调用析构函数,再去释放空间。
如果先释放空间,析构函数就找不到需要析构的对象了。
- 它们都是动态管理内存的入口。
- malloc/free是C/C++标准库的函数,new/delete是C++操作符。
- malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)。
- malloc/free需要手动计算类型大小且返回值会void*,new/delete可自己计算类型的大小,返回对应类型的指针。
operator new和operator delete
//定义
void * operator new (size_t size);
void operator delete (size_t size);
void * operator new[](size_t size);
void operator delete[] (size_t size);
//使用
int* p1 = (int*)operator new(4);
operator delete p1;
标准库函数operator new和operator delete的命名容易让人产生误解,与其他的operator函数(例如operator=)不同,这些函数没有重载new和delete表达式,实际上,我们不能重定义new和delete表达式的行为。
通过调用operator new函数执行new表达式获得内存,并接着在内存中构造一个对象。
通过撤销一个对象执行delete表达式,并接着调用operator delete函数,以释放该对象使用的内存。
- 思考
在这里,为什么我们不写delete[10],编译器也知道要调用10次析构函数呢?
如果开辟空间和释放空间的操作符不匹配会发生什么情况呢?
A* p0 = new A;
free(p0);//程序不会崩,因为虽然仅仅只是free没有析构,但是没有析构程序不一定会崩。
delete[] p0;//程序会崩,此时由上图可以知道,首先我们不会多开辟A大小的byte,但只要执行delete[],
//就会向前找A大小的byte,此时就是一种越界行为,程序就会崩溃。
A* p1 = new A[10];
free(p1);//程序会崩,从哪里申请的内存,就要从哪里释放。
delete p1;//程序会崩,因为释放的位置错了,应该从p1前几个字节开始释放。
int* p2 = new int[10];
delete p2;//程序不会崩,在这里使用内置类型申请空间的时候,不会多开辟4个字节,就不需要告诉delete要调用几次析构函数。
- 严格来说,new[]一定会多开辟空间,告诉delete需要调用多少次析构函数,当编译器识别到析构函数可掉可不调(内置类型)时,会优化,就不调用析构函数了。
- 总而言之,多不多开辟空间,取决于调不调用析构函数。