[C++系列] 44. C++内存泄露

1. 什么是内存泄露

首先我们需要明确,内存泄露是内存丢了吗?答案当然是否定的,不可能买了8G的内存运行程序之后变成了2G。内存是不会像物理意义上的丢失的,丢的只是指向这一片区域的指针。内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

2. 内存泄露中的典例

int main() {
	// 1.内存申请了忘记释放,严格意义上是有内存泄露的
	// 但实际意义上没有,程序结束之后,进程会将所有的资源还给系统
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	return 0;
}

使用上来讲有内存泄露,但程序结束后就将申请资源归还给系统。对于该类型的内存泄露很好解决。

比如说:这个程序申请了1G,后面继续申请了1G、1G...直至占满内存导致程序崩溃时,只需要将程序关闭或者重启主机即可将内存自动回收。类似于该类型的大片、大面积的内存泄露很好检查,或者程序运行几秒钟就崩掉了。若不是系统、服务器级的应用,该类型内存泄露危害不大。

而服务器需要持续工作,24小时不能停机,有专人看护,制冷系统,甚至放入底下深层给其降温,如果出现内存泄露导致服务器崩溃是一件严重亏损事情。对于这种不停机的应用大片、大片的内存泄露也不可怕,因为很容易被发现并且可以及时对其处理。可怕的是不停机的应用却有一小片一小片持续的内存泄露,很难被发现,有俗语:千里之堤毁于蚁穴,其像一个定时炸弹,可能1年内都没有问题,但是突然的某一天就引发服务器宕机,背后造成的损失可能直接导致一个公司从兴直接转衰。

int main() {
	// 2.异常安全问题
	int* p3 = new int[10];

	Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.

	delete[] p3;
}

如果有异常的话程序崩掉之后,内存不会被释放,因为没有执行下面的代码,直接跳到Catch或者其它地方,未执行delete,造成内存泄漏。这也是非常可怕的内存泄露!如果它也是不停机的应用,每次在这崩一下、Catch一下,内存就泄露一点,也是一颗“定时炸弹”!

3. 内存泄露分类

C/C++程序中一般我们关心两种方面的内存泄漏:
1)堆内存泄漏(Heap leak) 
堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
2)系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

4. 如何检测内存泄露

在linux下内存泄漏检测:linux下几款内存泄漏检测工具
在windows下使用第三方工具:VLD工具说明
其他工具:内存泄漏工具比较:内存泄漏工具比较

5. 如何避免内存泄漏 

1)工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。ps:这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智能指针来管理才有保证。
2) 采用RAII思想或者智能指针来管理资源。
3)有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4)出问题了使用内存泄漏工具检测。ps:不过很多工具都不够靠谱,或者收费昂贵。
总结一下:
内存泄漏非常常见,解决方案分为两种:

1)事前预防型。如智能指针(主要思想为:RAII,也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。它保证在任何情况下,使用对象时先构造对象,最后析构对象)等。

2)事后查错型。如泄漏检测工具。

6. 如何一次在堆上申请4G的内存

这也是一道面试题了,大多数同学的第一反应都是:不可能啊,怎么能够在堆上申请4G内存空间,我每次申请都告诉我编译不通过啊,再说啊,进程的地址空间就4G,而且我的内核就占1G,最多也只能申请3G啊,4G简直不可能的好吗???

其实每一个进程都有一个进程地址空间,每一个进程都有一个进程内核空间占1G左右大小。假如说我的电脑上运行了这么多程序,就有这么多进程,每个进程都占1G的话那我的电脑岂不是早就崩掉了?其实这些内核空间都指向同一个空间,是一个虚拟的 ,这个空间是不允许被使用的,每个进程都是这一段帮你使用。

我们之前编译失败原因是:我们编译的程序都是x32,其中32位最大的寻址空间或说最大的进程地址空间就是4G,并且内核还占1G,所以肯定是申请不到的。然而在x64位下,就是4G*4G个大小的寻址空间,每一个进程都享用4G*4G的内存大小,但实际上并不能完全使用。

// 将程序编译成x64的进程,运行下面的程序试试?
#include <iostream>
using namespace std;
 
int main() {
	int* p = new int[0x3fffffff]; //0011 1111 1111 1111 1111 1111 1111 1111
	system("pause");
	return 0;
}

修改编译平台为x64位即可申请,并且与电脑是否为x64系统没有任何关系,仅与编译程序位数有关。申请出来的都是虚拟空间,编译的地址都是虚拟地址,其真正与物理内存有一个映射关系。有一个页表,可以将程序的虚拟地址映射到物理地址。不是说你有64G就全部映射为64G,而是一片一片的进行映射使用,但内存价格昂贵,要不这样做的话内存的利用率也不高。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值