内存泄漏介绍

1.内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。最终的结果就是导致OOM。
2.内存泄漏是指你向系统申请分配内存进行使用(new),可是使用完了以后却不归还(delete),结果你申请到的那块内存你自己也不能再访问(也许你把它的地址给弄丢了),而系统也不能再次将它分配给需要的程序.
3.程序的内存空间
在这里插入图片描述

(1)栈区:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈区的内存是连续分配的,当我们声明变量时,那么编译器会自动接着当前栈区的结尾来分配内存,例如int a = 10.函数压入栈的顺序:返回地址->从右向左的函数参数->成员变量.
(2)堆区:从堆上开辟内存也叫动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现内存泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块,内存泄露指的就是此部分泄露。
(3)静态全局区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
(4)常量区:常量和字符串就是放在这里的。 程序结束后由系统释放
(5)代码区:存放函数体的二进制代码

int a = 0; //全局初始化区 
char *p1; //全局未初始化区 
void main() 
{
    int b; //栈 
    char s[] = “abc“;//栈 
    char *p2; //栈 
    char *p3 =123456; //123456\0在常量区,p3在栈上;体会与 char s[]="abc"; 的不同
    static int c =0//全局初始化区 
    p2 = (char *)malloc(20); //堆区
    strcpy(p1,123456); //123456\0在常量区,编译器可能将它与p3指向的 “123456 “优化成一块
}

4.内存泄露的原因主要就是new和delete没有搭配使用,申请的对象指针没有释放.解决办法就是采用智能指针.
关于这个,有篇文章讲的较为清楚:C++ 内存管理中内存泄漏问题产生原因以及解决方法

内存检查工具一:valgrind

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具,它主要包含了七个代码测试的子工具:
1.Memcheck (常用)
最常用的工具,用来检测程序中出现的内存问题,例如内存泄露,一切对malloc()/free()/new/delete的调用都会被捕获。
2.Callgrind (常用)
Callgrind收集程序运行时的一些数据,建立函数调用关系图,还可以有选择地进行cache模拟。它能够得到粒度为函数、代码行和指令级别的性能数据,具体来说,我们可以得到某个函数、某行代码、某条指令处累计执行了多少条指令,并可以对分析的数据进行可视化,如下图所示:
在这里插入图片描述

具体使用方法可以参考博客:Linux C/C++调试之三:性能分析工具callgrind的使用
3.Cachegrind
Cache分析器,它模拟CPU中的一级缓存I1,Dl和二级缓存,能够精确地指出程序中cache的丢失和命中。如果需要,它还能够为我们提供cache丢失次数,内存引用次数,以及每行代码,每个函数,每个模块,整个程序产生的指令数。这对优化程序有很大的帮助。
4.Helgrind (常用)
它主要用来检查多线程程序中出现的竞争问题。Helgrind寻找内存中被多个线程访问,而又没有一贯加锁的区域,这些区域往往是线程之间失去同步的地方,而且会导致难以发掘的错误。Helgrind实现了名为“Eraser”的竞争检测算法,并做了进一步改进,减少了报告错误的次数。不过,Helgrind仍然处于实验阶段。
5.Massif
堆栈分析器,它能测量程序在堆栈中使用了多少内存,告诉我们堆块,堆管理块和栈的大小。Massif能帮助我们减少内存的使用,在带有虚拟内存的现代系统中,它还能够加速我们程序的运行,减少程序停留在交换区中的几率。 此外,lackey和nulgrind也会提供。Lackey是小型工具,很少用到;Nulgrind只是为开发者展示如何创建一个工具。
使用方法:
1.安装
(1)直接安装:

sudo apt-get install valgrind

(2)或者源码安装:

wget https://sourceware.org/pub/valgrind/valgrind-3.15.0.tar.bz2
tar -xjvf valgrind-3.15.0.tar.bz2
cd valgrind-3.15.0/

因为valgrind支持多个平台,根据当前主机配置valgrind

./configure
make
sudo make install

查看一下版本看看是否安装好

valgrind --version

2.使用
(1)为了定位代码位置,需要在gcc编译时添加-g选项

gcc -Wall malloc1.c -g -o malloc1

对应与CmakeList.txt中,应添加选项:

add_definitions("-Wall -g")

(2)常用的指令:
查看设置项: valgrind --help
内存泄露检查: valgrind --tool=memcheck --leak-check=full ./oxfordmm
其中valgrind打印的日志过多,终端里看不完,所以需要输出日志文件,在文件里查看比较方便: valgrind --tool=memcheck --log-file=mem.log --leak-check=full ./oxfordmm
输出的日志文件如下图所示:
在这里插入图片描述
definitely lost项要重点关注,对应的错误信息可以在官网上查看:
http://valgrind.org/docs/manual/mc-manual.html#mc-manual.errormsgs
(4)方法总结,这个工具功能比较强大,用着也相对方便,但是运行起来太慢,拖慢原来程序速度10倍以上,不适用大规模的程序检测.而且输出信息比较冗余,找到有用的信息较为麻烦,所以推荐下一个工具,谷歌推出的AddressSanitizer.

内存检查工具二:AddressSanitizer(推荐)

Google旗下的开源工具AddressSanitizer(AScan)可以帮助我们检测内存泄露缓冲区溢出越界访问等内存错误,它非常快,只拖慢程序两倍左右.从gcc 4.8开始,AddressSanitizer成为gcc的一部分,可以直接使用,无需额外安装.
使用方法:
(1)在CmakeList.txt中添加特定的编译选项:
用-fsanitize=address选项编译和链接你的程序;
用-fno-omit-frame-pointer编译,以在错误消息中添加更好的堆栈跟踪

add_definitions("-Wall -g")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -fsanitize=leak")

如果需要也检查其他内存错误,也可以添加如下设置项:

add_definitions("-Wall -g")
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-omit-frame-pointer")

(2)直接运行程序就好:

./oxfordmm

实验结果如下图所示:
在这里插入图片描述
有的时候运行加上上述编译选项,启用分析工具时会产生错误直接退出,但是不启用工具的时候就不会出现这个现象.幸运的是工具会指出出错的位置,由图中可以看到位置在
:

UpdateBoundaryVoxel(ORB_SLAM2::KeyFrame*, int) /home/shiqi/cubeslam_orb_dot _oxfordmm/src/cube_slam/orb_object_slam/src/MapObject.cc:3706

中,直接改掉该行就好,然后运行效果如下图所示:
在这里插入图片描述

这个工具显示错误信息比较简约,干货较多,而且应用更加便捷,着实是首选的工具!

参考博客:
1.内存泄漏检测神器valgrind
2.AddressSanitizer使用介绍
3.C++ 内存管理中内存泄漏问题产生原因以及解决方法

Logo

更多推荐