【C++】内存泄漏详解

内存泄漏是什么

内存泄漏指的是由于疏忽或错误造成了程序未能释放掉不再使用的内存。

内存泄漏造成的后果

性能不良,内存会耗尽

内存泄漏的原因

  对于C++的内存泄漏,总结一句话:就是new出来的内存没有通过delete合理的释放掉!
下面总结几种常见的内存泄漏的场景:

  • 程序循环new创建出来的对象没有及时的delete掉,导致了内存的泄露
#include <iostream>
#include <new.h>

using namespace std;

void out_of_memroy()
{
    
    
    cout << "ERROR:内存已耗尽!" << endl;
}

int main()
{
    
    
    set_new_handler(out_of_memroy);//注意参数传递的是函数的地址;
    while(1)
    {
    
    
        new int[1000];
    }
    return 0;
}

以上代码会在堆区疯狂的动态分配内存空间,导致系统内存耗尽时自动调用set_new_handler参数列表中的函数,打印出ERROR:内存已耗尽!如下图:在这里插入图片描述

  • 在类的构造函数和析构函数中没有匹配的调用new和delete函数
      两种情况下会出现这种内存泄露:一是在堆里创建了对象占用了内存,但是没有显示地释放对象占用的内存;二是在类的构造函数中动态的分配了内存,但是在析构函数中没有释放内存或者没有正确的释放内存
  • 缺少拷贝构造函数和缺少重载赋值运算符
      这一种情况就是在类中涉及到资源的管理,类中默认的拷贝构造和赋值运算符重载都是浅拷贝的方式,一旦释放内存,就会导致一块内存被释放多次,产生内存泄漏。所以我们必须显示给出深拷贝方式的拷贝构造和赋值运算符重载
  • 没有将基类的析构函数定义为虚函数
      当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露

内存泄漏的解决方法

方法一:对象计数

方法:在对象构造时计数++,析构时–-,每隔一段时间打印对象的数量

优点:没有性能开销,几乎不占用额外内存。定位结果精确。

缺点:侵入式方法,需修改现有代码,而且对于第三方库、STL容器、脚本泄漏等因无法修改代码而无法定位。

方法二:重载new和delete

方法:重载new/delete,记录分配点(甚至是调用堆栈),定期打印。

优点:没有看出

缺点:侵入式方法,需将头文件加入到大量源文件的头部,以确保重载的宏能够覆盖所有的new/delete。记录分配点需要加锁(如果你的程序是多线程),而且记录分配要占用大量内存(也是占用的程序内存)。

方法三:Hook Windows系统API

方法:使用微软的detours库,hook分配内存的系统Api:HeapAlloc/HeapRealloc/HeapFree(new/malloc的底层调用),记录分配点,定期打印。

优点:非侵入式方法,无需修改现有文件(hook api后,分配和释放走到自己的钩子函数中),检查全面,对第三方库、脚本库等等都能统计到。

缺点:记录内存需要占用大量内存,而且多线程环境需要加锁。

方法四:使用DiagLeak检测

微软出品的内存泄漏分析工具,原理同hookapi方式。配合LDGraph可视化展示内存分配数据,更方便查找泄漏。

1.在IDE工程选项里面配置Release版本也生成调试信息,发布时,将pdb文件和exe文件一起发布。

2.程序运行后,打开LeakDiag,设置Symbol path

3.定期Log下目标进程的内存分配情况,通过LDGraph打印分配增长情况,来发现内存泄漏。

优点:同hookapi方法,非侵入式修改,无需做任何代码改动。跟踪全面。可视化分析堆栈一览无余!

缺点:对性能有影响,hook分配加锁,遍历堆栈。但是不会占用目标进程的自身内存。

猜你喜欢

转载自blog.csdn.net/Vcrossover/article/details/113756962