C++中的内存管理
1、C++中的内存分布
上图就是C和C++在内存中存储的分布图,由上到下,对应着内存中的高地址到低地址。此次我主要介绍的栈、堆数据段和代码段。
栈: 栈中存储着非静态的局部变量、函数参数和返回值等等,可参考上图箭头指向,并且在栈中数据的存储是由上到下的存储方式。
堆: 堆中存储的是动态内存分配的空间,且堆是由下向上存储的。
数据段(静态区): 用于存储全局变量和static修饰的后的变量。
代码段: 存储常量和可执行代码。
值得注意的是const修饰的变量,使变量成为不能被修改的常变量,这时就有小伙伴觉得,这是不是const修饰的变量,是不是都存储在静态区? 这个想法使错误的,const修饰的变量原来存储在哪不会因为const的修饰而改变。
2、C++中的内存管理方式
2.1、new/delete一个对象
new和delete与C语言中的malloc和free相似,但又不完全相同,且对于内置类型和自定义类型的new和delete也有些区别。
2.1.1 对内置类型new/delete
对于内置类型的new相当于malloc + memset,delete相对于free + 指针变量制空。具体new和delete怎么使用,请看下面代码及测试结果:
int main()
{
//下面是new使用方法
int* a1 = new int;//new + 类型。 不初始化时等价于malloc
int* a2 = (int*)malloc(sizeof(int));
int* b = new int(3);//new + 类型(初始化参数),可直接开辟空间+初始化
int* c = new int[6]{0, 0, 0, 0, 0, 0}; //new + 类型[数组大小]{初始化参数}
//相当于 malloc + memeset;定义一个数组且初始化
//下面是delete使用方法
delete a1;//delete + 变量名 , 释放空间 + 指针制空
free(a2);
a2 = nullptr;
delete b;
delete[] c;//delete[] + 指针变量名
//数组在delete的时候就需要在delete后面加[],不加编译也能过但是有存在风险
return 0;
}
图1:new一个对象的测试结果
图2:delete一个对象的测试结果
2.1.2 对自定义类型new/delete
对(类)自定义类型new等价于malloc + 构造函数初始化,delete等价于析构函数 + free,值得注意的是delete自定义类型最后不会把指针变量制空。具体使用方法如下:
#include <ostream>
#include <iostream>
using namespace std;
class A{
public:
A()
: a(nullptr)
{
cout << "A()" << endl;//用于查看构造函数是否调用
}
~A()
{
cout << "~A()" << endl;//用于查看析构函数是否调用
}
private:
int* a;
};
int main()
{
A* aa = new A;//new + 类名
delete aa;//delete + 实例化的对象名
A* bb = new A[5];// new + 类名[数组大小],开辟类数组
delete[] bb;//delete[] + 实例化的对象名
//释放数组时必须加[],否则可能会出错
return 0;
}
2.2、operator new/delete
new/delete是C++中的动态内存申请/释放的操作符(不是函数),其实它们的底层是通过调用operator new/delete这两个系统提供的全局函数,故new一个对象等价于"operator new + 构造函数",delete等价于“析构函数 + operator delete”,那就有小伙伴想operator new和malloc有什么区别,其实operator new底层调用的就是malloc,只不过如果operator new失败了会抛异常(后面的程序将不执行,可能会有空间释放不了,照成内存泄漏),而malloc失败只是返回空指针;同样operator delete底层调用的就是free函数。
2.2.1 operator new/delete的类专属重载
operator new/delete 还可以用于类专属重载,可以自定义operator new/delete函数,从已有内存池中取空间,这就涉及到池化技术,用于给类中开辟出单独的一个空间,这样类使用空间的时候就不用再去总内存空间中调用,直接使用自己申请好的空间就可以,方便管理,且效率更高。就像你们家要喝水你每次去河边挑一桶水,下次用还要去在挑一桶水,这样是不是很麻烦,如果河边挤满了人,你还要等它提好了水你才能去提水,那如果你直接在自己家门口弄个蓄水池,下次要喝水就不用去河边挑水,直接去家门口提一桶就可以,这就很方便。具体代码如下:
void* operator new(size_t n)
{
void* p = nullptr;
p = allocator<ListNode>().allocate(1);
cout << "memory pool allocate" << endl;
return p;
}
void operator delete(void* p)
{
allocator<ListNode>().deallocate((ListNode*)p, 1);
cout << "memory pool deallocate" << endl;
}
2.3、定位new
operator new从内存池中要空间,而定位new就是给这块已经取好的空间调用构造函数进行初始化。具体操作如下:
A* p = (A*)operator new(sizeof(A));
new(p)A; // new(p)A(3); // 定位new,placement-new,显示调用构造函数初始化这块对象空间
3、总结
3.1、malloc,calloc和realloc的区别
malloc向系统获取空间,calloc向系统获取空间并将空间初始化为0,而realloc为重新分配空间,会在已有的空间后面扩容,如果已有的空间后面没空间,重新开辟空间,并且复制之前空间中的内容到新空间中,自动释放旧空间,也就是说,realloc后,指针地址可能发送改变。
3.2、malloc/free和new/delete的区别
1、new/delete是操作符,malloc/free是函数。
2、malloc时需要手动输入变量类型大小 ( 如sizeof(int) ),而new 后面直接加类型就可以直接得到变量大小。
3、malloc返回值是void*类型,而new直接返回指定类型,不用强转。
4、对于内置类型new = 开辟空间 + 初始化,对于自定义类型new = 开辟空间 + 构造函数malloc是只开辟空间;对于内置类型delete = 释放空间 + 指针变量制空,对于自定义类型delete = 析构函数 + 释放空间(指针变量不置空),free是只释放空间。
5、new失败会抛异常,后面的代码都不执行,但malloc只是返回空指针。
3.3、内存泄漏
1、什么是内存泄漏?。
内存泄漏就是指因为疏忽或错误导致未能正常释放已经不再使用的内存空间,导致失去了对这块内存空间的控制。随着程序运行时间越久内存泄漏越多,系统能用的内存也越少,导致系统越来越卡。
2、怎么解决内存泄漏?。
(1)提前预防,使用智能指针,(2)使用泄漏检测工具,检测泄漏。