什么是内存泄漏?

Posted -YIN

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是内存泄漏?相关的知识,希望对你有一定的参考价值。

内存泄漏

是什么? 与几种常见情况

什么是内存泄漏?

内存泄漏是因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。 其实就是内存在程序运行中动态申请的内存空间由于某种原因程序未释放或无法释放。

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

堆内存泄露有如以下几种示例:

  1. 申请内存忘记释放
void test1()
   // 申请空间未释放
	int* p1 = (int*)malloc(sizeof(int));
	int* p2 = new int;
	
	int* p = new int[5];
	fun(); // 出现异常导致 delete[] 没有执行。
	delete[] p;

  1. 错误操作导致内存被丢失

改变指针指向导致找不到指针被重新赋值前指向的内存。

void test2()

	char * p1 = (char *)malloc(10);
	char * np = (char *)malloc(10);
	p1 = np;  //p1以前所指向的内存位置变成了孤立的内存,它无法释放
    
    int *p2 = new int; 
    p2 = new int;//错误

在 C++ 中如果有涉及动态申请的内存空间在浅拷贝时也容易出现内存泄漏的现象(原理如上例)

错误释放内存

struct SeqList
	int* arr;
	size_t size;
	size_t capacity;    // 容量空间大小
;

void Seqlist()
	struct SeqList s;
	struct SeqList* ps = &s;
	ps->arr = (int* )malloc(sizeof(int)* 10);
	free(ps);
	// 应 free(ps->arr);


继承中的内存泄漏

当使用父类指针指向子类对象,子类中涉及动态开辟的内存,在释放 (delete) 父类对象指针时只会调用父类的析构函数而不调用子类的,造成子类申请空间没有释放导致内存泄漏。

class A

public:
	A(int a) : _a(a)
		cout << "A()" << endl;
	
	~A()   /*virtual ~A() */
		cout << "~A()" << endl;
	
protected:
	int _a;
;

class B : public A

public:
	B(int b, int a): A(a), _b(b)
		cout << "B()" << endl;
		_p = new int[10];
	
	~B()
		delete[] _p;
		cout << "~B()" << endl;
	
protected:
	int _b;
	int* _p;
;

int main()

	A* pa = new B(1, 2);
	delete pa;
	_CrtDumpMemoryLeaks();
	return 0;


运行结果:


所以一般在继承中,最好将基类析构函数设置为虚函数,会构成析构函数的重写
当派生类中涉及动态内存管理一定要将析构设为虚函数,防止出现内存泄露

如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。虽然函数名不相同,看起来违背了重写的规则,其实不然,这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor。


内存泄漏导致程序崩溃

存在危害

在 我们平时写的代码练习中,我们感受不到内存泄漏的存在,检测较为严格的IDE最多会中断使程序崩溃,而且当程序运行结束后所有内存都重新被系统回收,所以似乎看起来对我们影响不大。

但是对于长期运行的程序,如操作系统,后端服务器来说如果出现内存泄漏,会造成很大的影响和损失,可能会导致响应越来越慢最终卡死。


上图就是死循环动态申请内存的内存增长变化图,有兴趣的小伙伴可以拿自己电脑试一下记得先保存好重要的东西[doge]

	while (1)
	
		Sleep(2000);
		int* p = (int *)malloc(sizeof(int)* 100000000);
	


内存泄漏 和 内存溢出

内存溢出 OOM (out of memory),是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个int,但给它存了long才能存下的数,那就是内存溢出。

而内存泄漏是未正确处理内存的导致无法使用。一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。

举一个通俗的例子:
内存泄漏是你去公厕想上厕所却发现好几个厕所里没人但是却锁着门上面贴着正在维修无法使用,你想进去进不了;
而内存溢出是眼前只有一间厕所但是却有 >= 1个人和你一起进去了(也许能上也许不行,但都没法正常上厕所)。


解决方案

检测内存泄漏

  1. 是否存在内存泄漏?
  2. 定位内存泄漏。(在哪里发生)

常用的检测工具

Linux环境下C/C++:

Valgrind是一个GPL的软件,用于Linux(For x86, amd64 and ppc32)程序的内存调试和代码剖析。你可以在它的环境中运行你的程序来监视内存的使用情况,比如C 语言中的malloc和free或者 C++中的new和 delete。使用Valgrind的工具包,你可以自动的检测许多内存管理和线程的bug,避免花费太多的时间在bug寻找上,使得你的程序更加稳固。

mtrace GNU扩展, 用来跟踪malloc, mtrace为内存分配函数(malloc, realloc, memalign, free)安装hook函数

dmalloc 用于检查C/C++内存泄露(leak)的工具,即检查是否存在直到程序运行结束还没有释放的内存,以一个运行库的方式发布

memwatch 和dmalloc一样,它能检测未释放的内存、同一段内存被释放多次、位址存取错误及不当使用未分配之内存区域

更多详见:https://elinux.org/Memory_Debuggers
参考大佬:Linux下几款C++程序中的内存泄露检查工具

Windows Visual Studio中:
VLD(Visual LeakDetector)内存泄露库
_CrtDumpMemoryLeaks()接口可以检测出异常的内存块和其字节大小。

内存泄露检测工具比较


后续将逐个使用验证,完善博文

如何避免内存泄漏

  1. 养成良好习惯,申请的空间一定要及时释放。(每次分配都要有对应的freedelete
  2. 注意几种常见错误情况,确保程序每块内存都不会丢失(如指针改变指向…)
  3. 采用RAII思想(Resource acquisition is initialization)或使用智能指针管理资源。(自动化管理)
  4. 如果出现内存泄漏使用内存检测工具进行检测。

以上是关于什么是内存泄漏?的主要内容,如果未能解决你的问题,请参考以下文章

什么样的内存泄漏 XCode Analyzer 可能不会注意到?

此Loader类应该是静态的,否则可能会发生泄漏内部类内存泄漏| AsyncTaskLoader内存泄漏 -

C++:内存泄漏

C语言中的指针和内存泄漏

为啥.NET 没有内存泄漏?

这可能是内存泄漏吗?