- C语言中的动态内存函数有malloc、free、calloc、realloc四个函数,详细介绍请看C语言之动态内存管理(动态内存函数) 。
一、C/C++内存分布
- 栈,又叫堆栈,非静态局部变量/函数参数/返回值等等,栈是向下增长的。
- 内存映射段,是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享内存,做进程间通信。
- 堆,用于程序运行时动态内存分配,堆是可以向上增长的。
- 数据段,存储全局数据和静态数据。
- 代码段,可以执行的代码/只读常量。
二、C++动态申请内存
- C++中通过new和delete操作符进行动态内存管理。
1.对于内置类型的操作
- 申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。
#include <iostream>
using namespace std;
void test()
{
// 动态申请一个int类型的空间
int* ptr = new int;
//对应的delete
delete ptr;
// 动态申请一个int类型的空间并且初始化为5
int* ptr2 = new int(5);
//对应的delete
delete ptr2;
//动态申请5个int类型的空间(动态数组)
int* ptr3 = new int[3];
//对应的delete
delete[] ptr3;
}
int main()
{
test();
return 0;
}
2.对于自定义类型的操作
- 在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与free不会。
#include <iostream>
using namespace std;
namespace sheena
{
class Test
{
public:
Test()
:_t(0)
{
cout << "Test()构造函数:" << this << endl;
}
~Test()
{
cout << "~Test()析构函数:" << this << endl;
}
private:
int _t;
};
}
void test1()
{
// 申请单个Test类型的空间
sheena::Test* ptr1 = (sheena::Test*)malloc(sizeof(sheena::Test));
// 对应的释放空间
free(ptr1);
// 申请10个Test类型的空间
sheena::Test* ptr2 = (sheena::Test*)malloc(sizeof(sheena::Test) * 10);
// 对应的释放空间
free(ptr2);
}
void test2()
{
// 申请单个Test类型的空间
sheena::Test* ptr1 = new sheena::Test;
// 对应的释放空间
delete ptr1;
// 申请10个Test类型的空间
sheena::Test* ptr2 = new sheena::Test[10];
// 对应的释放空间
delete[] ptr2;
}
int main()
{
cout << "test1()malloc和free:" << endl;
test1();
cout << endl;
cout << "test2()new和delete:" << endl;
test2();
cout << endl;
return 0;
}
3.operator new 与 operator delete函数
- operator new 和 operator delete都不是重载函数。
- new和delete是用户进行动态内存申请和释放的操作符,operator new和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间的,delete在底层通过operator delete全局函数来释放空间的。
- operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间失败,尝试执行空间不足应对措施,如果该应对措施用户设置了,则继续申请,否则抛出异常。
- operator delete:该函数最终通过free来释放空间的。
using namespace sheena;
void test3()
{
// 申请单个Test类型的空间 + 申请失败返回0
Test* ptr1 = (Test*)malloc(sizeof(Test));
// 申请单个Test类型的空间 + 申请失败抛出异常(用户若没设置)
Test* ptr2 = (Test*)operator new(sizeof(Test));
// 申请单个Test类型的空间 + 申请失败抛出异常 + 初始化(调用构造函数)
// 也就是operator new + 初始化(调用构造函数)
Test* ptr3 = new Test;
}
4.new和delete的实现原理
(1)对于内置类型
- 如果申请的是内置类系的空间,new和malloc,delete和free基本类似。
- new和malloc,delete和free不同的地方是:new/delete申请和释放的是单个元素的空间,new[]/delete[]申请和释放的是连续空间。
- new在申请空间失败时会抛出异常,而malloc是直接返回NULL。
(2)对于自定义类型
- new的原理:调用operator new函数申请空间,然后在申请的空间上执行构造函数,完成对象的初始化。
- delete的原理:在要释放的空间上执行析构函数,完成对象中资源的清理工作。然后调用operator delete函数释放对象的空间。
- new T[ N]的原理:调用operator new[]函数,在operator new[]函数中实际用operator new函数完成对N个对象空间的申请,然后在申请的空间上执行N次构造函数,完成N个对象的初始化。
- delete[]的原理:在要释放的对象空间上执行N此析构函数,完成N个对象中资源的清理。然后调用operator delete[]函数,在operator delete[]函数中实际调用operator delete函数来释放空间。
5.new的定位表达式
- 定位new表达式是在已存在的原始内存空间中调用构造函数初始化一个对象。
- 使用场景:new的定位表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定位表达式进行显式调用构造函数进行初始化。
#include <iostream>
using namespace std;
namespace sheena
{
class Test
{
public:
Test()
:_t(0)
{
cout << "Test()构造函数:" << this << endl;
}
~Test()
{
cout << "~Test()析构函数:" << this << endl;
}
private:
int _t;
};
}
using namespace sheena;
void test()
{
//在内存池中申请空间
char* pool = (char*)malloc(1024 * 4);
Test* ptr = (Test*)pool;
pool += sizeof(Test);
//new的定位表达式
new(ptr)Test;
ptr->~Test();
ptr = nullptr;
pool -= sizeof(Test);//将释放的空间还给内存池
}
int main()
{
test();
return 0;
}
5.malloc/free和new/delete的区别
- malloc和free是函数,new和delete是操作符。
- malloc申请的空间没有初始化,new申请的空间可以初始化。
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可。(Test* ptr1 = (Test*)malloc(sizeof(Test)),Test* ptr3 = new Test;)
- malloc的返回值是void*,在使用时必须强转,new不需要强转,因为new后跟的是空间的类型。
- malloc申请空间失败时,返回的是NULL,因此使用时必须要判断是否为空。而new申请空间失败时会抛出异常,因此在使用时不需要判空,但是需要捕获异常。
- 在对自定义类型对象空间的申请时,malloc/free只会申请和释放空间。而new在申请空间之后会调用构造函数完成对象的初始化,delete在释放空间之前会调用析构函数完成对象中资源的清理。
- 当是在内存池中申请空间和不能抛异常的场景下使用malloc和free。
三、内存泄漏
1.内存泄漏的定义
- 内存泄漏指的是因为疏忽或者错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为错误,失去了对该段内存的控制,因而造成了内存的浪费。
2.内存泄漏的危害
- 长期运行的程序出现内存泄漏,影响会很大,如操作系统、后台服务等等。出现内存泄漏会导致设备响应越来越慢,最终卡死。
3.内存泄漏的分类
- 堆内存泄漏:堆内存值的是程序执行中依据要分配通过malloc、calloc、realloc、new等从堆中分配的一块内存,用完后必须通过调用相应的free、delete释放掉。如果程序的设计错误导致部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
- 系统资源泄露:指程序使用系统分配的资源,如套接字、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
4.如何避免
- 申请的空间内存使用完毕之后一定要释放。
- 尽量避免在堆上分配内存。尽可能的使用栈上的内存。
- 采用RAII思想或者智能指针来管理资源。