一分钟速面🍜

c++的内存泄露是指:由于某种原因,程序动态申请的内存没有被正确释放,从而造成内存浪费甚至导致程序崩溃。c++内存泄漏主要包括:主动申请内存后未释放导致的堆内存泄漏;无法释放导致的内存泄漏;未释放系统资源导致资源泄漏;野指针、悬挂指针导致泄漏;未将需要被其他类继承的类的析构函数声明为virtual时导致泄漏。


堆内存泄漏

程序中使用new或malloc主动申请内存后,未使用delete或free主动释放,导致系统分配的堆内存未被释放。这会使程序占用的内存不断增加,最终可能耗尽系统资源,导致性能下降或程序崩溃。
即使在作用域内部主动分配内存,作用域结束后也并不会自动释放这段内存,需要在适当的时候手动释放这块内存。

解决方法

  • 有申请就有释放,养成良好的代码习惯。
  • new/delete和malloc/free配套不混用:new/delete在分配和释放内存的基础上会调用对象的构造函数和析构函数,而malloc/free只会分配和释放内存,不会调用构造函数和析构函数。
  • new[]申请一定delete[]删除:如果通过new[]申请一个对象数组,将会计算其所占内存大小并进行分配,并且调用构造函数,如果使用delete时,虽然可以清除分配的内存,但仅调用数组中第一个对象的析构函数,而使用delete[ ],则会逐个调用析构函数

无法释放导致泄漏

当申请的内存因为某些原因无法释放,将会导致内存泄漏

  • 智能指针循环引用:shared_ptr通过引用计数管理内存的释放,当计数为0时释放内存,如果两个shared_ptr互相引用,则每个shared_ptr的计数都至少是1,无法释放
class A {
public:
  	shared_ptr<B> ptr_a;
};
class B {
public:
	shared_ptr<A> ptr_b;
};
int main() {
  	shared_ptr<A> a(new A());
  	shared_ptr<B> b(new B());
	a->ptr_b = b;	// a中有指向b的shared_ptr
	b->ptr_a = a;	// b中有指向a的shared_ptr
  	return 0;
}
  • 内存丢失:程序动态分配一段堆内存,但无指针指向该块内存,导致此块内存无法被定位,无法再被回收。
int *ptr = NULL;
ptr = new int; // 申请一块堆内存,ptr指向该内存
*ptr = 1;
int i = 2;
ptr = &i;	// ptr指向其他内存,原先申请的内存无法定位
return 0;

解决方法

  • 搭配使用weak_ptr和shared_ptr解决循环引用
  • 养成良好代码规范,不再使用的内存应该及时释放。

系统资源泄漏

程序使用系统资源(文件句柄、网络连接、线程等)后,未及时释放导致内存泄漏。

  • 打开文件未关闭:如果程序处理完一个文件后没有关闭文件句柄,该程序就会持续占用包括内存和文件描述符等系统资源,这些资源将无法被释放给其他程序使用。
void func() {
    FILE* file = fopen('something.txt', 'r');
    // 忘记关闭文件
    // fclose(file)
    return ;
}
  • 建立网络连接未关闭。

解决方法

  • 使用RAII封装资源:RAII通过构造函数获取资源,析构函数释放资源,作用域结束自动释放系统资源。如c++标准库的std::fstream等。
  • 养成良好代码习惯。

野指针、悬挂指针导致泄漏

  • 野指针:指针变量没有被初始化或被赋予了一个无意义的值,导致指针指向随机的内存。导致程序结果不可预测,甚至导致文件损坏。
  • 悬挂指针:指针被free/delete后没有置空,free/delete只是释放掉指针所指向的内存,指针还指向那块已被释放的内存,后续再次访问会出错。

解决方法

  • 预防野指针:创建指针时将指针初始化,让其指向合法位置或置空
  • 预防悬挂指针:释放指针指向的内存后,将指针置空或释放

未将基类析构函数声明为虚函数

  • 基类需要被继承且需要通过基类的指针删除派生类实例时,需要将基类的析构函数声明为virtual。
  • 因为通过基类的指针删除派生子类的实例时,如果基类析构函数不为virtual,则只会调用基类的析构函数,而不会调用子类的析构函数,导致子类的资源泄漏。

正春华枝俏,待秋实果茂,愿与君共勉

更多推荐