目录
C++的内存分布
C++内存管理方式
new和delete的使用方法
申请内置类型
申请自定义类型
malloc/free和new/delete的区别
operator new 和operator delete函数
内存泄漏
内存泄漏分类
如何避免内存泄漏?
C++的内存分布
在内存里面是分好几个区的
- 栈又叫堆栈:非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段:高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信
- 堆:用于程序运行时动态内存分配,堆是可以上增长的。
- 数据段(静态区):存储全局数据和静态数据。
- 代码段(常量区):可执行的代码/只读常量
C++内存管理方式
提到C++的内存管理方式,我们不得不先提到C语言的内存管理方式 malloc/calloc/realloc/free
C语言:(malloc、free、calloc、realloc)函数讲解-CSDN博客
首先,C语言内存管理的方式在C++中可以继续使用。但有些地方就无能为力而且使用起来比较麻烦,因此C++又提出了自己的内存管理方式:通过new和delete(操作符)进行动态内存管理。
new和delete的使用方法
申请内置类型
一、申请单个某类型的空间
//动态申请单个int类型的空间int* p1 = new int; //申请delete p1; //销毁//动态申请单个int类型的空间并初始化为10int* p1 = new int(10); //申请 + 赋值delete p1; //销毁
等价于:
//动态申请单个int类型的空间int* p1 = (int*)malloc(sizeof(int)); //申请free(p1); //销毁//动态申请一个int类型的空间并初始化为10int* p1 = (int*)malloc(sizeof(int)); //申请*p1 = 10; //赋值free(p1); //销毁
二、申请多个某类型的空间
//动态申请10个int类型的空间int* p2 = new int[10]; //申请delete[] p2; //销毁//动态申请10个int类型的空间并初始化为0到9int* p2 = new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; //申请 + 赋值delete[] p2; //销毁
等价于:
//动态申请10个int类型的空间int* p2 = (int *)malloc(sizeof(int)* 10); //申请free(p2); //销毁//动态申请10个int类型的空间并初始化为0到9int* p2 = (int*)malloc(sizeof(int)* 10); //申请for (int i = 0; i < 10; i++) //赋值{p2[i] = i;}free(p2); //销毁
注意:申请和释放单个元素的空间,使用new和delete操作符;申请和释放连续的空间,使用new[ ]和delete[ ]。
申请自定义类型
#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;
};
一、申请单个类的空间
A* p1 = new A; //申请delete p1; // 销毁
new 和 delete 调用了构造函数和析构函数
C语言:
A* p1 = (A*)malloc(sizeof(A)); //申请free(p1); //销毁
malloc和 free 调用了构造函数和析构函数
二、申请多个类的空间
A* p2 = new A[3]; //申请delete[] p2; //销毁
C语言:
A* p2 = (A*) malloc(sizeof(A)* 10);
free(p2);
注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc和free不会。
总结
- C++中如果是申请内置类型的对象或是数组,用new/delete和malloc/free没有什么区别。
- 如果是自定义类型,区别很大,new和delete分别是开空间+构造函数、析构函数+释放空间,而malloc和free仅仅是开空间和释放空间。
- 建议在C++中无论是内置类型还是自定义类型的申请和释放,尽量都使用new和delete
malloc/free和new/delete的区别
共同点
都是从堆上申请空间,并且需要用户手动释放。
不同点:
- malloc和free是函数,new和delete是操作符。
- malloc申请的空间不会初始化,new申请的空间会初始化。
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可。
- malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型。
- malloc申请失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常。
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数和析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
new/delete | malloc/free |
new和delete是操作符 | malloc和free是函数 |
malloc申请的空间不会初始化 | new申请的空间会初始化 |
malloc申请空间时,需要手动计算空间大小并传递 | new只需在其后跟上空间的类型即可 |
malloc的返回值是void*,在使用时必须强转 | new不需要 |
malloc申请失败时,返回的是NULL,因此使用时必须判空 | new不需要,但是new需要捕获异常 |
不会调用构造函数和析构函数 | 会调用构造函数和析构函数 |
operator new 和operator delete函数
- new 和 delete是用户进行动态内存申请和释放的操作符
- operator new 和 operator delete是系统提供的全局函数
- new和delete在底层是通过调用全局函数 operator new 和 operator delete来申请和释放空间的。
- operator new 和 operator delete的用法和malloc 和 free的用法完全一样,其功能都是在堆上申请和释放空间。
int* p1 = (int*)operator new(sizeof(int)* 10); //申请operator delete(p1); //销毁
等价于:
int* p2 = (int*)malloc(sizeof(int)* 10); //申请free(p2); //销毁
- operator new的底层是通过调用malloc函数来申请空间的,当malloc申请空间成功时直接返回,malloc 在内存分配失败时返回NULL。operator new在无法分配足够内存时,会抛出
std::bad_alloc
异常 - 而operator delete的底层是通过调用free函数来释放空间的
注意:虽然说operator new和operator delete是系统提供的全局函数,但是我们也可以针对某个类,重载其专属的operator new和operator delete函数,进而提高效率。
内存泄漏
内存泄漏
内存泄漏是指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:
长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。
内存泄漏分类
1、堆内存泄漏(Heap Leak)
堆内存指的是程序执行中通过malloc、calloc、realloc、new等从堆中分配的一块内存,用完后必须通过调用相应的free或者delete释放。假设程序的设计错误导致这部分内容没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
2、系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
如何避免内存泄漏?
- 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记住匹配的去释放。
- 采用RAII思想或者智能指针来管理资源。
- 有些公司内部规范使用内部实现的私有内存管理库,该库自带内存泄漏检测的功能选项。
- 出问题了使用内存泄漏工具检测。
内存泄漏非常常见,解决方案分为两种:
- 事前预防型。如智能指针等。
- 事后查错型。如泄漏检测工具。