文章目录
- 1.operator new()和operator delete()
- 2.new记录分配的内存大小供delete使用
- 3.new[]/delete[]申请和释放一个数组
- 3.1 基本数据类型(内置类型)
- 3.2 自定义类型(类类型)
- 4.new/delete和new[]/delete[]要配对使用
1.operator new()和operator delete()
operator new() 和 operator delete() 是函数。
new 干了两个事情:
- 在堆中分配内存:通过 operator new() 来分配内存
- 调用构造函数来初始化内存
delete 也干了两个事:
- 调用析构函数
- 释放内存:调用 operator delete() 来释放内存
2.new记录分配的内存大小供delete使用
int *p = new int; // 4字节
delete p;
删除的时候,编译器怎么知道要回收的是 4 字节?
答:new 内部有记录机制,记录了分配出去多少内存。
new 如何记录分配的内存大小供 delete 使用?
答:不同的编译器,new 内部有不同的实现方式。
3.new[]/delete[]申请和释放一个数组
3.1 基本数据类型(内置类型)
举例1:
#include <iostream>
using namespace std;
int main()
{
int* p = new int(100); // 如果不释放,会泄漏4字节
delete p;
return 0;
}
举例2:
#include <iostream>
using namespace std;
int main()
{
int* p = new int[2]; // 如果不释放,会泄漏8字节
delete[] p;
return 0;
}
3.2 自定义类型(类类型)
举例1:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A::A()" << endl;
}
~A()
{
cout << "A::~A()" << endl;
}
};
int main()
{
A* p = new A(); // 如果不释放,会泄漏1字节
delete p;
return 0;
}
举例2:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A::A()" << endl;
}
~A()
{
cout << "A::~A()" << endl;
}
};
int main()
{
A* p = new A[2](); // 如果不释放,会泄漏6字节
delete[] p;
return 0;
}
疑问:为什么给类型 A 对象数组动态分配内存时多出来 4 个字节,而给内置类型 int 数组动态分配内存时并没有多出来 4 字节?
在上面的程序中,对于类类型 A,调用了两次构造函数、两次析构函数,也就是说,delete一个数组时,要为每一个数组元素调用析构函数。
但是,对于 delete 表达式,它并不知道数组的元素个数,只有 operator new() 函数和 operator delete() 函数知道。因此,必须有一种手段来告诉 delete 表达式的数组大小是多少。
那么,一种可行的方式就是,多分配一个大小为 4 字节的空间来记录数组大小,同时可以约定前 4 字节来记录大小。那么,由 operator new() 函数分配的地址与 new 表达式返回的地址应该相差 4 个字节。
当然,对于非类类型数组和不需要调用析构函数的类类型数组,这多余的 4 字节就不需要了。
4.new/delete和new[]/delete[]要配对使用
举例1:
#include <iostream>
using namespace std;
int main()
{
int* pi = new int[3]; // 如果不释放,会泄漏12字节
//delete pi; // 这里即使用delete释放,也不会发生内存泄漏
delete[] pi; // 这种释放方法才是最规范的
return 0;
}
举例2:
#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A::A()" << endl;
}
// 没有自定义的析构函数
};
int main()
{
A* pa = new A[2](); // 如果不释放,会泄漏2字节
//delete pa; // 这里即使用delete释放,也不会发生内存泄漏
delete[] pa; // 这种释放方法才是最规范的
return 0;
}
举例3:
#include <iostream>
using namespace std;
class A
{
public:
A()
{
cout << "A::A()" << endl;
}
~A()
{
cout << "A::~A()" << endl;
}
};
int main()
{
A* pa = new A[2](); // 如果不释放,会泄漏6字节
//delete pa; // 这里如果用delete释放,系统就会报告异常
delete[] pa; // 这种释放方法才是最规范的
return 0;
}
为什么自己一提供析构函数,不用 delete[]
来释放 new[]
出来的内存就报异常呢?
从上图中可以看出,只调用了 1 次 A 的析构函数而不是 2 次,表示肯定有内存泄漏。真正释放内存的是 operator delete() 函数,而多出来的 4 个字节导致释放内存空间错乱。
结论:如果一个对象(内置对象、类对象),使用 new[]
来分配内存,却用单独的 delete
(而不是 delete[]
)来释放内存,那么这个对象需要满足的条件是:对象的类型要么是内置类型或者无自定义的析构函数的类类型。