C/C++内存泄漏及检测

2023-11-14

本文参考内存泄漏检测
“该死系统存在内存泄漏问题”,项目中由于各方面因素,总会有人抱怨存在内存泄漏,系统长时间运行之后,可用内存越来越少,甚至导致了某些服务失败。内存泄漏是最难发现的常见错误之一,因为除非用完内存或调用malloc失败,否则都不会导致任何问题。实际上,使用C/C++这类没有垃圾回收机制的语言时,你很多时间都花在处理如何正确释放内存上,如果内存运行时间足够长,如后台进程运行在服务器上,只要服务器不宕机就一直运行,一个小小的失误也会对程序造成重大的影响,如造成某些关键服务失败;

内存泄漏简介及后果

wikipedia中这样定义内存泄漏:在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

最难捉摸也最难检测到的错误之一是内存泄漏,即未能正确释放以前分配的内存的 bug。 只发生一次的小的内存泄漏可能不会被注意,但泄漏大量内存的程序或泄漏日益增多的程序可能会表现出各种征兆:从性能不良(并且逐渐降低)到内存完全用尽。 更糟的是,泄漏的程序可能会用掉太多内存,以致另一个程序失败,而使用户无从查找问题的真正根源。 此外,即使无害的内存泄漏也可能是其他问题的征兆。

内存泄漏会因为减少可用内存的数量从而降低计算机的性能。最终,在最糟糕的情况下,过多的可用内存被分配掉导致全部或部分设备停止正常工作,或者应用程序崩溃。内存泄漏可能不严重,甚至能够被常规的手段检测出来。在现代操作系统中,一个应用程序使用的常规内存在程序终止时被释放。这表示一个短暂运行的应用程序中的内存泄漏不会导致严重后果。

在以下情況,内存泄漏导致较严重的后果:

  • 程序运行后置之不理,并且随着时间流逝消耗越来越多的内存(比如服务器的后台任务,尤其是嵌入式系统的后台任务,这些任务可能被运行后很多年内都置之不理);
  • 新的内存被频繁分配,比如当显式电脑游戏或动画视频画面时;
  • 程序能够请求未被释放的内存(比如共享内存,甚至在程序终止的时候);
  • 泄漏在OS内部发生;
  • 泄漏在系统关键驱动中发生;
  • 内存非常有限,比如在嵌入式系统或便携设备中;
  • 当运行于一个终止时内存并不自动释放的操作系统之上,而且一旦丢失只能通过重启来恢复;

下面我们通过以下例子来介绍如何检测内存泄漏问题:

#include <stdlib.h>
#include <iostream>
using namespace std;

void GetMemory(char *p, int num)
{
    p = (char*)malloc(sizeof(char) * num);//使用new也能够检测出来
}

int main(int argc,char** argv)
{
    char *str = NULL;
    GetMemory(str, 100);
    cout<<"Memory leak test!"<<endl;
    //如果main中存在while循环调用GetMemory
    //那么问题将变得很严重
    //while(1){GetMemory(...);}
    return 0;
}

实际中不可能这么简单,如果这么简单也用不着别的方法,程序员一眼就可以看出问题,此程序只用于测试。

Windows平台下的内存泄漏检测

检测是否存在内存泄漏问题

Windows平台下面Visual Studio调试器和C 运行时(CRT)库为我们提供了检测和识别内存泄漏的有效方法,原理大致如下:内存分配要通过CRT在运行时实现,只要在分配内存和释放内存时分别做好记录,程序结束时对比分配内存的记录就可以确定是不是内存泄漏。在VS中启用内存检测的方法如下:

  • STEP1,在程序中包括以下语句: (#include 语句必须采用上文所示顺序。 如果更改了顺序,所使用的函数可能无法正常工作。)
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h> 

通过包含crtdbg.h,将malloc 和 free函数映射到它们的调试版本中,即__malloc_dbg 和 __free_dbg,这两个函数将追踪内存分配和释放。此映射只在调试版本(在其中定义了__DEBUG)中发生,发布版本使用普通的malloc 和 free 函数;

#define语句将CRT 堆函数的基版本映射到相应的”Debug”版本,并非绝对需要该语句;并非绝对需要该语句,但如果没有该语句,内存泄漏转储包含的有用信息将很少;

  • STEP2, 在添加了上述语句之后,可以通过在程序中包括以下语句(通常应恰好放在程序退出位置之前)来转储内存泄漏信息:
_CrtDumpMemoryLeaks();

此时完整代码如下所示:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#include <iostream>
using namespace std;

void GetMemory(char *p, int num)
{
    p = (char*)malloc(sizeof(char) * num);
}

int main(int argc,char** argv)
{
    char *str = NULL;
    GetMemory(str, 100);
    cout<<"Memory leak test!"<<endl;
    _CrtDumpMemoryLeaks();
    return 0;
}

当在调试器下运行程序时,_CrtDumpMemoryLeaks 将在“输出”窗口中显示内存泄漏信息。 内存泄漏信息如下所示:
这里写图片描述

如果没有使用 #define _CRTDBG_MAP_ALLOC 语句,内存泄漏转储将如下所示:

这里写图片描述

未定义 _CRTDBG_MAP_ALLOC 时,所显示的会是:

  • 内存分配编号(在大括号内);
  • 块类型(普通、客户端或 CRT);

    • “普通块”是由程序分配的普通内存;
    • “客户端块”是由 MFC 程序用于需要析构函数的对象的特殊类型内存块。 MFC new 操作根据正在创建的对象的需要创建普通块或客户端块;
    • “CRT 块”是由 CRT 库为自己使用而分配的内存块。 CRT 库处理这些块的释放,因此您不大可能在内存泄漏报告中看到这些块,除非出现严重错误(例如 CRT 库损坏);

      从不会在内存泄漏信息中看到下面两种块类型:

    • “可用块”是已释放的内存块;

    • “忽略块”是您已特别标记的块,因而不出现在内存泄漏报告中;
  • 十六进制形式的内存位置;

  • 以字节为单位的块大小;
  • 前16字节的内容(亦为十六进制);

定义了 _CRTDBG_MAP_ALLOC 时,还会显示在其中分配泄漏的内存的文件。 文件名后括号中的数字(本示例中为 10)是该文件中的行号。

注意:如果程序总是在同一位置退出,调用 _CrtDumpMemoryLeaks 将非常容易。 如果程序从多个位置退出,则无需在每个可能退出的位置放置对 _CrtDumpMemoryLeaks 的调用,而可以在程序开始处包含以下调用:

_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

该语句在程序退出时自动调用_CrtDumpMemoryLeaks。 必须同时设置 _CRTDBG_ALLOC_MEM_DF_CRTDBG_LEAK_CHECK_DF 两个位域,如前面所示。

定位具体的内存泄漏地方

通过上面的方法,我们几乎可以定位到是哪个地方调用内存分配函数malloc和new等,如上例中的GetMemory函数中,即第10行!但是不能定位到,在哪个地方调用GetMemory()导致的内存泄漏,而且在大型项目中可能有很多处调用GetMemory。如何要定位到在哪个地方调用GetMemory导致的内存泄漏?

定位内存泄漏的另一种技术涉及在关键点对应用程序的内存状态拍快照,CRT 库提供了一种结构类型 _CrtMemState, 可以用它来存储内存状态的快照:

_CrtMemState s1, s2, s3;

若要在给定点对内存状态拍快照,请向 _CrtMemCheckpoint 函数传递 _CrtMemState 结构。 该函数用当前内存状态的快照填充此结构:

_CrtMemCheckpoint( &s1 );

通过向 _CrtMemDumpStatistics 函数传递 _CrtMemState 结构,可以在任意点转储该结构的内容:

_CrtMemDumpStatistics( &s1 );

若要确定代码中某一部分是否发生了内存泄漏,可以在该部分之前和之后对内存状态拍快照,然后使用 _CrtMemDifference 比较这两个状态:

_CrtMemCheckpoint( &s1 );
// memory allocations take place here
_CrtMemCheckpoint( &s2 );

if ( _CrtMemDifference( &s3, &s1, &s2) )
   _CrtMemDumpStatistics( &s3 );

顾名思义,_CrtMemDifference 比较两个内存状态(s1 和 s2),生成这两个状态之间差异的结果(s3)。 在程序的开始和结尾放置 _CrtMemCheckpoint 调用,并使用_CrtMemDifference 比较结果,是检查内存泄漏的另一种方法。 如果检测到泄漏,则可以使用 _CrtMemCheckpoint 调用通过二进制搜索技术来划分程序和定位泄漏。

如上面的例子程序我们可以这样来定位确切的调用GetMemory的地方:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

#include <iostream>
using namespace std;

_CrtMemState s1, s2, s3;

void GetMemory(char *p, int num)
{
    p = (char*)malloc(sizeof(char) * num);
}

int main(int argc,char** argv)
{
    _CrtMemCheckpoint( &s1 );
    char *str = NULL;
    GetMemory(str, 100);
    _CrtMemCheckpoint( &s2 );
    if ( _CrtMemDifference( &s3, &s1, &s2) )
        _CrtMemDumpStatistics( &s3 );
    cout<<"Memory leak test!"<<endl;
    _CrtDumpMemoryLeaks();
    return 0;
}

调试时,程序输出如下结果:
这里写图片描述

这说明在s1和s2之间存在内存泄漏!!!如果GetMemory不是在s1和s2之间调用,那么就不会有信息输出。

Linux平台下的内存泄漏检测

在上面我们介绍了,VS中在代码中包含 crtdbg.h,将malloc 和 free 函数映射到它们的调试版本,即__malloc_dbg 和 __free_dbg,这两个函数将跟踪内存分配和释放。此映射只在调试版本(在其中定义了__DEBUG)中发生,发布版本使用普通的malloc 和 free 函数。即为malloc和free做了钩子,用于记录内存分配信息。

Linux下面也有原理相同的方法——mtrace,http://en.wikipedia.org/wiki/Mtrace。方法类似,我这就不具体描述,参加给出的链接。这节我主要介绍一个非常强大的工具valgrind。如下图所示:
这里写图片描述

如上图所示知道:

==6118== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==6118== at 0x4024F20: malloc (vg_replace_malloc.c:236)
==6118== by 0x8048724: GetMemory(char*, int) (in /home/netsky/workspace/a.out)
==6118== by 0x804874E: main (in /home/netsky/workspace/a.out)

是在main中调用了GetMemory导致的内存泄漏,GetMemory中是调用了malloc导致泄漏了100字节的内存。

Things to notice:
• There is a lot of information in each error message; read it carefully.
• The 6118 is the process ID; it’s usually unimportant.
• The first line (“Heap Summary”) tells you what kind of error it is.
• Below the first line is a stack trace telling you where the problem occurred. Stack traces can get quite large, and be
confusing, especially if you are using the C++ STL. Reading them from the bottom up can help.

• The code addresses (eg. 0x4024F20) are usually unimportant, but occasionally crucial for tracking down weirder bugs.

The stack trace tells you where the leaked memory was allocated. Memcheck cannot tell you why the memory leaked,
unfortunately. (Ignore the “vg_replace_malloc.c”, that’s an implementation detail.)
There are several kinds of leaks; the two most important categories are:
• “definitely lost”: your program is leaking memory – fix it!
• “probably lost”: your program is leaking memory, unless you’re doing funny things with pointers (such as moving
them to point to the middle of a heap block)

总结

其实内存泄漏的原因可以概括为:调用了malloc/new等内存申请的操作,但缺少了对应的free/delete,总之就是,malloc/new比free/delete的数量多。我们在编程时需要注意这点,保证每个malloc都有对应的free,每个new都有对应的deleted!!!平时要养成这样一个好的习惯。

要避免内存泄漏可以总结为以下几点:

  • 程序员要养成良好习惯,保证malloc/new和free/delete匹配;
  • 检测内存泄漏的关键原理就是,检查malloc/new和free/delete是否匹配,一些工具也就是这个原理。要做到这点,就是利用宏或者钩子,在用户程序与运行库之间加了一层,用于记录内存分配情况。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++内存泄漏及检测 的相关文章

随机推荐

  • av_register_all和avformat_alloc_context

    av register all 被声明为已否决其实就是av register all 这个方法在FFMPEG 4 0以后将不再推荐使用 而且是非必需的 因此直接注释掉即可 avformat alloc context 用来申请AVForma
  • 后台网页设计

    后台网页设计 陈子龙 2019 5 17 这是一张我自己设计的后台网站的首页图片 我的这张后台网站图中的文字亲密性有点高 板块之间的距离间距都是相差不多的 视觉上给人的感觉也不是很高 在这里插入图片描述 这个logo放的有点大 我的一个交互
  • 内储管理技术——连续分配方式

    早期的操作系统 只支持单进程 为了实现简单 在执行程序之前 操作系统必须将待执行的程序全部装入内存 这就是简单存储管理技术 现代操作系统 支持多进程并发执行 允许进程装入部分程序即可以开始执行 其余部分保留在磁盘上 当执行所需的部分不在内存
  • Android 横向滚动列表 (类似横向ListView)

    示例 布局代码 xml
  • STM32基本IO的寄存器介绍

    STM32基本IO的寄存器介绍 说明 简介 端口配置低寄存器 32位 数据寄存器 32位 置位 复位寄存器 32位 复位寄存器 16位 锁存寄存器 32位 说明 最近自己在学习STM32 跟着原子哥的STM32教学视频学习 参考的资料有来自
  • 一个将军的人生总结

    这十句话 是我几十年间学习 积累 感悟出来的 我认为 做到了 就有益健康 第一句话 结交 两个朋友 一个是运动场 一个是图书馆 到运动场锻炼身体 强健体魄 到图书馆博览群书 不断地 充电 蓄电 放电 第二句话 培养 两种功夫 一个是本分 一
  • 前端基础——DOM

    前端基础学习第六天 文章目录 前端基础学习第六天 一 DOM节点操作 1 节点是什么 2 创建节点 3 添加节点 4 改变节点值 5 删除节点 6 复制 克隆 节点 二 DOM事件流 1 阻止事件冒泡和默认事件 默认行为
  • Ant Design Pro学习记录—ModalForm的使用(一)

    目录 前言 一 ModalForm销毁 二 ModalForm编辑赋值 三 ProFormUploadButton赋值 四 其它 总结 前言 使用了AntDesignPro 仿照TableList创建了自己的列表 列表添加编辑确成了困扰 添
  • py2neo框架学习全网最详细教程

    py2neo框架学习 1 公式模式是语法范式 2 代码模式是实例 可按顺序复制执行 3 该教程全部使用py2neo的内置方法 不使用run运行neo4j 4 涉及所有可能的节点 节点类型 属性 关系 关系类型 属性的增删改查 1 连接图模型
  • 《C语言运算符100例》优先级面试错题

    建议先阅读基础教学 十万字C语言动漫教程 一 前言 运算符优先级一直是让人头疼的东西 趁着 字节取消大小周 的势头来临 通宵整理了一个思维导图出来 希望对你有所帮助 这篇文章 我会仔细分析这张思维导图 所有的 C语言运算符的用法都在这里了
  • minikube踩坑记

    minikube如何使用本地镜像 需要执行 eval minikube docker env 然后再重新打镜像 并运行即可 docker build t foo 0 0 1 kubectl run hello foo image foo 0
  • Android Things——SPI通信

    1 SPI 1 1 概念 SPI是串行外设接口 Serial Peripheral Interface 的缩写 SPI 是一种高速的 全双工 同步的通信总线 并且在芯片的管脚上只占用四根线 节约了芯片的管脚 同时为PCB的布局上节省空间 提
  • 顺序存储结构的插入与删除

    1 获得元素的操作 要获得线性表中第i个元素的值 只要i的数值在数组下标范围内 就把i 1下标的值返回即可 define OK 1 define ERROR 0 define FALSE 0 define TRUE 1 typedef in
  • 线性方程组的直接解法c语言,2 线性方程组的直接解法

    2 1 例题解答 例 2 1 用Gauss消元法解方程组 解 直接建立求解该方程组的M文件Gauss m如下 求解例题2 1 高斯法求解线性方程组Ax b A为输入矩阵系数 b为方程组右端系数 方程组的解保存在x变量中 先输入方程系数 A
  • GPIO / LED驱动编程开发

    GPIO LED 驱动开发编程 一般来说 GPIO的Pin都是可选功能的 GPIO x selects the function of GPIO Pin 为什么 不要问 操作 1 将某一个GPIO选择功能复用为GPIO 2 将该GPIO选择
  • MIT6.00 1x Lecture 1 - Introduction to Computation 学习笔记

    l MIT6 00 1x 麻省理工 计算机科学和Python编程导论 Lecture 1 Introduction to Computation 计算科学简介 1 1 Basics of computation 计算科学基础 Goal 本课
  • opencv形状目标检测

    1 圆形检测 OpenCV图像处理中 找圆技术 的使用 图像处理 双翌视觉OpenCV图像处理中 找圆技术 的使用 图像处理 双翌视觉https www shuangyi tech com news 224 htmlopencv 找圆心得
  • .h5文件的写入和读取(HDF5)

    先理解 h5文件的数据组织方式 h5文件中有两个核心的概念 组 group 和数据集 dataset 一个h5文件就是 dataset 和 group 二合一的容器 dataset 简单来讲类似数组组织形式的数据集合 像 numpy 数组一
  • python数据结构课堂笔记5:排序与查找

    排序与查找 文章目录 排序与查找 查找算法 顺序查找 算法分析 二分查找 算法分析 排序算法 冒泡排序和选择排序算法 冒泡排序Bubble Sort
  • C/C++内存泄漏及检测

    本文参考内存泄漏检测 该死系统存在内存泄漏问题 项目中由于各方面因素 总会有人抱怨存在内存泄漏 系统长时间运行之后 可用内存越来越少 甚至导致了某些服务失败 内存泄漏是最难发现的常见错误之一 因为除非用完内存或调用malloc失败 否则都不