文章目录
- 一.内存管理
- 1. 内存布局
- 2. C++的内存管理
- ①内置类型
- ② 自定义类型
- 3. operate new 与 operate delete
- ① 解读operate new源代码
- ② 解读operate delete源代码
- 4. new和delete的基本原理
- ①new对类对象
- ②delete对类对象
- 拓展—— 深入理解delete[]和new[]
- 对比 C和C++内存管理的用法
- 5. 定位new
- 基本用法
- 6. 内存泄漏
一.内存管理
1. 内存布局
- 说明:内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
- 说明:调用函数的传参是在栈上开辟空间的,且从右向左进行传参。函数的代码的执行也是在栈上的,不过是调用代码区的代码,在栈上执行而已。
- 更多关于malloc/realloc/calloc/free以及内存空间的证明的信息:内存管理。
2. C++的内存管理
- 区别于malloc等申请空间的函数,C++引出了new操作符
- 区别于free释放空间的函数,C++引出了delete操作符
- 说明: new和delete一定要配套使用。
①内置类型
#include<iostream>
using namespace ::std;
int main()
{
int* p = new int(3);
delete p;
return 0;
}
图解:
② 自定义类型
- 数组
#include<iostream>
using namespace ::std;
int main()
{
int* p = new int[6]{0};
delete[] p;
return 0;
}
- 类
#include<iostream>
using namespace ::std;
class Date
{
public:
Date(int year)
{
cout << "Date" << endl;
_year = year;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
};
int main()
{
Date* p = new Date(1949);
delete p;
Date* p1 = new Date[4]{1949,2019,2022,2023};
delete[] p1;
Date* p2 = new Date[4]{ Date(1949),Date(2019),Date(2022),Date(2023) };
delete[] p2;
return 0;
}
程序运行结果:
图解:
- 说明:对类来说,new一个对象会调用此对象的构造函数进行初始化,然而malloc一个对象仅仅是对一个对象开辟了空间,并没有调用构造函数 。
3. operate new 与 operate delete
- 补充:异常,简单地说就是代码执行到某处中断了,无法继续执行,这时的情况就叫异常。
平常我们在遇到对空指针解引用的情况时,就是出现异常了。
图:
① 解读operate new源代码
void* __CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
void* p;
while ((p = malloc(size)) == 0)
if (_callnewh(size) == 0)
{
// 如果申请内存失败了,这里会抛出bad_alloc 类型异常
static const std::bad_alloc nomem;
_RAISE(nomem);
}
return (p);
}
图解:
- 因此:operate new 是一个函数,调用了malloc,并且如果开辟失败会出现异常。
- 为什么要这样做呢?因为面向对象语言不喜欢用返回值来处理问题,而更喜欢使用抛异常,来解决问题,通过异常我们可以判断出现了什么情况的错误。
② 解读operate delete源代码
void operator delete(void* pUserData)
{
_CrtMemBlockHeader* pHead;
RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
if (pUserData == NULL)
return;
_mlock(_HEAP_LOCK);
__TRY
pHead = pHdr(pUserData);
_ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
_free_dbg(pUserData, pHead->nBlockUse);
__FINALLY
_munlock(_HEAP_LOCK);
__END_TRY_FINALLY
return;
}
图解:
- 因此:operate delete释放空间的操作跟free本质上是相同的。
那了解了这两个运算符重载,之后我们就可以进一步探究new和delete了。
4. new和delete的基本原理
代码:
#include<iostream>
using namespace ::std;
class Date
{
public:
Date(int year)
{
cout << "Date" << endl;
_year = year;
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
};
int main()
{
Date* p = new Date(1949);
delete p;
return 0;
}
①new对类对象
代码分析:
Date* p = new Date(1949);
图解:
②delete对类对象
代码分析:
delete p;
图解:
进入此句汇编代码:
拓展—— 深入理解delete[]和new[]
- 讨论范围:类对象数组
- 在我们创建一个类对象数组时,知道调用多少次构造函数,但在销毁时,我们怎么知道要调用多少次析构函数?
- 换句话说——就是new[]做了什么,可以让delete[]省去这个找到有多少个对象的操作。
实验代码:
#include<iostream>
using namespace ::std;
int main()
{
Date* arr = new Date[4]{1,2,3,4};
delete[] arr;
return 0;
}
- 答案很简单,new[]在开辟数组的空间的前4个字节,放了数组的相关信息,有多少个对象,数组的大小等信息,这样delete[]就能通过指针来找到相关信息,从而进行操作。
那这样的代码呢?
#include<iostream>
using namespace ::std;
int main()
{
Date* arr = new Date[4]{1,2,3,4};
delete arr;
return 0;
}
- 这样delete将不知道调用几次析构函数,从而可能会出现内存泄漏,这是很可怕的现象。
- 因此:new[] 和delete[]一定要配套使用。
对比 C和C++内存管理的用法
- malloc和free是函数,new和delete是操作符
- malloc申请的空间不会初始化,new可以初始化
- malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
- malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
- malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常
- 申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理。
5. 定位new
- 定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。
基本用法
#include<iostream>
using namespace ::std;
int main()
{
Date* p2 = (Date *)operator new(sizeof(Date));
new(p2)Date(1949);//如果有默认构造,括号可以不写。
delete p2;
return 0;
}
- 一般我们会在池化技术中使用,就好比原来你家没有厕所,你上厕所,要去公共厕所,比较远,厕所还可能有人,但是现在你家门口造了厕所,别人要用还要通过你同意,这是不是方便了许多,池化技术与此雷同,也是为了提高效率。
6. 内存泄漏
- 内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
看这样一段代码:
#include<iostream>
using namespace ::std;
int main()
{
char* p = new char[1024 * 1024 * 1024];
return 0;
}
这个程序在编译器上会导致内存泄漏吗?
答案是——不会的。因为编译器会在程序结束时,帮你释放这些空间,这就跟手机卡了,关机再重启的道理是一样的,但是如果是一个24*7小时不停的程序呢?比如服务器,如果存在内存泄漏,如果说大了很快就会被检查出来,但是如果一次只泄漏几个字节,那么这台服务器会越用越卡,最终导致服务器宕机——出事故了,那相关人员就得跟着遭殃…
- 分类
1.系统资源泄漏:
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
2.堆内存泄漏(Heap leak)
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
那该如何解决内存泄漏呢?
1.养成良好的编程习惯,对开辟的空间即用即销毁。
2.合理利用检查内存泄漏的工具,通常这类工具比较昂贵。
3.利用RAII思想——资源获取即初始化,或者智能指针——充分地体现了RAII思想。
4.公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
5.私有内存管理库,就是公司内部实现的,这套库自带内存检查功能。