在 Linux 平台中调试 C/C++ 内存泄漏方法

2023-05-16

由于 C 和 C++ 程序中完全由程序员自主申请和释放内存,稍不注意,就会在系统中导入内存错误。同时,内存错误往往非常严重,一般会带来诸如系统崩溃,内存耗尽这样严重的后果。从历史上看,来自计算机应急响应小组和供应商的许多最严重的安全公告都是由简单的内存错误造成的。自从 70 年代末期以来,C/C++ 程序员就一直讨论此类错误,但其影响在 2007 年仍然很大。与许多其他类型的常见错误不同,内存错误通常具有隐蔽性,即它们很难再现,症状通常不能在相应的源代码中找到。例如,无论何时何地发生内存泄漏,都可能表现为应用程序完全无法接受,同时内存泄漏不是显而易见[1]。存在内存错误的 C 和 C++ 程序会导致各种问题。如果它们泄漏内存,则运行速度会逐渐变慢,并最终停止运行;如果覆盖内存,则会变得非常脆弱,很容易受到恶意用户的攻击。

因此,出于这些原因,需要特别关注 C 和 C++ 编程的内存问题,特别是内存泄漏。本文先从如何发现内存泄漏,然后是用不同的方法和工具定位内存泄漏,最后对这些工具进行了比较,另外还简单介绍了资源泄漏的处理(以句柄泄漏为例)。本文使用的测试平台是:Linux (Redhat AS4)。但是这些方法和工具许多都不只是局限于 C/C++ 语言以及 linux 操作系统。

内存泄漏一般指的是堆内存的泄漏。堆内存是指程序从堆中分配的、大小任意的(内存块的大小可以在程序运行期决定)、使用完后必须显示的释放的内存。应用程序一般使用malloc、realloc、new 等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用 free 或 delete 释放该内存块。否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

1. 如何发现内存泄漏

有些简单的内存泄漏问题可以从在代码的检查阶段确定。还有些泄漏比较严重的,即在很短的时间内导致程序或系统崩溃,或者系统报告没有足够内存,也比较容易发现。最困难的就是泄漏比较缓慢,需要观测几天、几周甚至几个月才能看到明显异常现象。那么如何在比较短的时间内检测出有没有潜在的内存泄漏问题呢?实际上不同的系统都带有内存监视工具,我们可以从监视工具收集一段时间内的堆栈内存信息,观测增长趋势,来确定是否有内存泄漏。在 Linux 平台可以用 ps 命令,来监视内存的使用,比如下面的命令 (观测指定进程的VSZ值):


ps -aux
  





回页首


2. 静态分析

包括手动检测和静态工具分析,这是代价最小的调试方法。

2.1 手动检测

当使用 C/C++ 进行开发时,采用良好的一致的编程规范是防止内存问题第一道也是最重要的措施。检测是编码标准的补充。二者各有裨益,但结合使用效果特别好。专业的 C 或 C++ 专业人员甚至可以浏览不熟悉的源代码,并以极低的成本检测内存问题。通过少量的实践和适当的文本搜索,您能够快速验证平衡的 *alloc() 和 free() 或者 new 和 delete 的源主体。人工查看此类内容通常会出现像清单 1 中一样的问题,可以定位出在函数 LeakTest 中的堆变量 Logmsg 没有释放。


清单1. 简单的内存泄漏

                
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int LeakTest(char * Para)
{
        if(NULL==Para){
                //local_log("LeakTest Func: empty parameter\n");
                return -1;
        }
        char * Logmsg = new char[128];
        if(NULL == Logmsg){
                //local_log("memeory allocation failed\n");
                return -2;
        }
        sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);
        //local_log(Logmsg);
        return 0;
}
int   main(int argc,char **argv )
{
        char szInit [] = "testcase1";
        LeakTest(szInit);
        return 0;
}
  

2.2 静态代码分析工具

代码静态扫描和分析的工具比较多,比如 splint, PC-LINT, BEAM 等。因为 BEAM 支持的平台比较多,这以 BEAM 为例,做个简单介绍,其它有类似的处理过程。

BEAM 可以检测四类问题: 没有初始化的变量;废弃的空指针;内存泄漏;冗余计算。而且支持的平台比较多。

BEAM 支持以下平台:

  • Linux x86 (glibc 2.2.4)
  • Linux s390/s390x (glibc 2.3.3 or higher)
  • Linux (PowerPC, USS) (glibc 2.3.2 or higher)
  • AIX (4.3.2+)
  • Window2000 以上

清单2. 用作 Beam 分析的代码

                
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int *p;

void
foo(int a)
{
  int b, c;

  b = 0;
  if(!p) 
    c = 1;

  if(c > a)
    c += p[1];
}

int LeakTest(char * Para)
{
        char * Logmsg = new char[128];
        if((Para==NULL)||(Logmsg == NULL))
                return -1;        
        sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);        
        return 0;
}

int   main(int argc,char **argv )
{
        char szInit [] = "testcase1";
        LeakTest(szInit);
        return 0;
}
  

下面以 X86 Linux 为例,代码如清单 2,具体的环境如下:

OS: Red Hat Enterprise Linux AS release 4 (Nahant Update 2)

GCC: gcc version 3.4.4

BEAM: 3.4.2; https://w3.eda.ibm.com/beam/

可以把 BEAM 看作一个 C/C++ 编译器,按下面的命令进行编译 (前面两个命令是设置编译器环境变量):


./beam-3.4.2/bin/beam_configure  --c gcc
./beam-3.4.2/bin/beam_configure  --cpp g++
./beam-3.4.2/bin/beam_compile  --beam::compiler=compiler_cpp_config.tcl  -cpp code2.cpp
  

从下面的编译报告中,我们可以看到这段程序中有三个错误:”内存泄漏”;“变量未初始化”;“ 空指针操作”


"code2.cpp", line 10: warning: variable "b" was set but never used
    int b, c;
        ^

BEAM_VERSION=3.4.2
BEAM_ROOT=/home/hanzb/memdetect
BEAM_DIRECTORY_WRITE_INNOCENTS=
BEAM_DIRECTORY_WRITE_ERRORS=

-- ERROR23(heap_memory)     /*memory leak*/     >>>ERROR23_LeakTest_7b00071dc5cbb458
"code2.cpp", line 24: memory leak
ONE POSSIBLE PATH LEADING TO THE ERROR:
 "code2.cpp", line 22: allocating using `operator new[]' (this memory will not be freed)
 "code2.cpp", line 22: assigning into `Logmsg'
 "code2.cpp", line 24: deallocating `Logmsg' because exiting its scope 
                       (losing last pointer to the memory)

-- ERROR1     /*uninitialized*/     >>>ERROR1_foo_60c7889b2b608
"code2.cpp", line 16: uninitialized `c'
ONE POSSIBLE PATH LEADING TO THE ERROR:
 "code2.cpp", line 10: allocating `c'
 "code2.cpp", line 13: the if-condition is false
 "code2.cpp", line 16: getting the value of `c'

 VALUES AT THE END OF THE PATH:
  p != 0 


-- ERROR2     /*operating on NULL*/     >>>ERROR2_foo_af57809a2b615
"code2.cpp", line 17: invalid operation involving NULL pointer
ONE POSSIBLE PATH LEADING TO THE ERROR:
 "code2.cpp", line 13: the if-condition is true (used as evidence that error is possible)
 "code2.cpp", line 16: the if-condition is true
 "code2.cpp", line 17: invalid operation `[]' involving NULL pointer `p'

 VALUES AT THE END OF THE PATH:
  c = 1 
  p = 0 
  a <= 0
  

2.3 内嵌程序

可以重载内存分配和释放函数 new 和 delete,然后编写程序定期统计内存的分配和释放,从中找出可能的内存泄漏。或者调用系统函数定期监视程序堆的大小,关键要确定堆的增长是泄漏而不是合理的内存使用。这类方法比较复杂,在这就不给出详细例子了。





回页首


3. 动态运行检测

实时检测工具主要有 valgrind, Rational purify 等。

3.1 Valgrind

valgrind 是帮助程序员寻找程序里的 bug 和改进程序性能的工具。程序通过 valgrind 运行时,valgrind 收集各种有用的信息,通过这些信息可以找到程序中潜在的 bug 和性能瓶颈。

Valgrind 现在提供多个工具,其中最重要的是 Memcheck,Cachegrind,Massif 和 Callgrind。Valgrind 是在 Linux 系统下开发应用程序时用于调试内存问题的工具。它尤其擅长发现内存管理的问题,它可以检查程序运行时的内存泄漏问题。其中的 memecheck 工具可以用来寻找 c、c++ 程序中内存管理的错误。可以检查出下列几种内存操作上的错误:

  • 读写已经释放的内存
  • 读写内存块越界(从前或者从后)
  • 使用还未初始化的变量
  • 将无意义的参数传递给系统调用
  • 内存泄漏

3.2 Rational purify

Rational Purify 主要针对软件开发过程中难于发现的内存错误、运行时错误。在软件开发过程中自动地发现错误,准确地定位错误,提供完备的错误信息,从而减少了调试时间。同时也是市场上唯一支持多种平台的类似工具,并且可以和很多主流开发工具集成。Purify 可以检查应用的每一个模块,甚至可以查出复杂的多线程或进程应用中的错误。另外不仅可以检查 C/C++,还可以对 Java 或 .NET 中的内存泄漏问题给出报告。

在 Linux 系统中,使用 Purify 需要重新编译程序。通常的做法是修改 Makefile 中的编译器变量。下面是用来编译本文中程序的 Makefile:


CC=purify gcc
  

首先运行 Purify 安装目录下的 purifyplus_setup.sh 来设置环境变量,然后运行 make 重新编译程序。


./purifyplus_setup.sh
  

下面给出编译一个代码文件的示例,源代码文件命名为 test3.cpp. 用 purify 和 g++ 的编译命令如下,‘-g’是编译时加上调试信息。


purify g++ -g test3.cpp –o test
  

运行编译生成的可执行文件 test,就可以得到图1,可以定位出内存泄漏的具体位置。


./test
  


清单3. Purify 分析的代码

                
#include <unistd.h> 
 char * Logmsg;

int LeakTest(char * Para)
{
        if(NULL==Para){
                //local_log("LeakTest Func: empty parameter\n");
                return -1;
        }
        Logmsg = new char[128];
		for (int i = 0 ; i < 128; i++)
			Logmsg[i] = i%64;

        if(NULL == Logmsg){
                //local_log("memeory allocation failed\n");
                return -2;
        }
        sprintf(Logmsg,"LeakTest routine exit: '%s'.\n", Para);
        //local_log(Logmsg);
        return 0;
}

int   main(int argc,char **argv )
{
        char szInit [] = "testcase1";
		int i;
         LeakTest(szInit);
		for (i=0; i < 2; i++){
			if(i%200 == 0)
				LeakTest(szInit);
			sleep(1);
		}        
        return 0;
}
  

需要指出的是,程序必须编译成调试版本才可以定位到具体哪行代码发生了内存泄漏。即在 gcc 或者 g++ 中,必须使用 "-g" 选项。


图 1 purify 的输出结果
 




回页首


结论

本文介绍了多种内存泄漏,定位方法(包括静态分析,动态实时检测)。涉及到了多个工具,详细描述的它们的用法、用途以及优缺点。对处理其它产品或项目内存泄漏相关的问题有很好的借鉴意义。



参考资料

学习
  • 内存调试技巧一文介绍了内存错误的分类和危害性,并且给出了一些良好的和内存相关的编码实践。 

  • 实时内存检测工具 Virgrind 的介绍和使用方法,可以参考 Virgrind 主页 

  • 有关 Rational Purify 的使用方法,可以参考 Rational Purify 使用及分析实例。 

  • 内存管理内幕一文介绍了linux内存管理的机制,希望对大家实地操作有所启发。 

  • 访问 developerWorks Linux 专区,查找面向 Linux 开发人员的更多参考资料,并阅读 最受欢迎的文章和教程。 

  • 查看 developerWorks 上所有的 Linux 技巧和 Linux 教程。 

  • 随时关注 developerWorks 技术活动和网络广播。 


获得产品和技术
  • 订购 SEK for Linux,共包含两张 DVD,其中有用于 Linux 的最新 IBM 试用软件,包括 DB2®、 Lotus®、Rational®、Tivoli® 和 WebSphere®。 

  • 用可直接从 developerWorks 下载的 IBM 试用软件 构建您的下一个 Linux 开发项目。 


讨论
  • 通过 新的 developerWorks 空间 中的博客、论坛、podcast 和社区主题加入 developerWorks 社区。


作者简介

 

韩兆兵,IBM 中国软件开发中心工程师,在 WVS and EVV 组工作,从事 Websphere VoiceServer 的技术支持工作。


 

刘盈,IBM 软件开发中心工程师,在 WVS and EVV 组工作,从事 WebSphere Voice Server 相关产品的软件测试和技术支持工作。


 

强晟,IBM 中国软件开发中心工程师,在 WVS and EVV 组工作,从事 WebSphere Voice Server 相关产品的软件测试和技术支持工作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Linux 平台中调试 C/C++ 内存泄漏方法 的相关文章

  • 在iOS上绘制自然的签名

    在iOS上绘制自然的签名 这里有一篇很棒的文章写如何在Android上获取流畅的签名 xff1a Smoother Signatures xff0c 但是我没有找到一篇是写在iOS上如何实现 那么 xff0c 究竟怎么做才能在iOS设备上获
  • xxx is not in the sudoers file解决方法

    用sudo时提示 34 xxx is not in the sudoers file This incident will be reported 其中XXX是你的用户名 xff0c 也就是你的用户名没有权限使用sudo 我们只要修改一下
  • linux-查看端口状态

    Linux如何查看端口状态 方法步族 xff1a 1 在Linux使用过程中 xff0c 需要了解当前系统开放了哪些端口 xff0c 并且要查看开放这些端口的具体进程和用户 xff0c 可以通过netstat命令进行简单查询 2 netst
  • (转载)C++中枚举与字符串相互转换

    有的时候我们喜欢使用一些外部的文件保存管理一些配置信息 xff0c 这些配置文件大多都是文本格式例如ini xff0c xml等 xff0c 这样方便编辑和管理 因此在使用的过程中必然会遇到各种字符串转换问题 最常见的便是将字符串的数字转换
  • INSERT INTO 表名 SELECT 语句

    Connected to Oracle Database 11g Enterprise Edition Release 11 2 0 1 0 Connected as scott CREATE TABLE 表名 AS SELECT 语句 S
  • Windows 去除快捷方式小箭头真正无任何副作用的方法!

    前几天写过 Windows7去除快捷方式小箭头后会导致左侧收藏夹中的桌面图标消失 xff0c 以为那种方法已经很完美 xff0c 然而我错了 xff0c 当我右击 计算机 想打开 管理 时 xff0c 弹出了错误提示 xff1a 该文件没有
  • 在 KDE 下不能正常使用 fcitx 以及翻页问题

    在 kde 下使用 fcitx 的时候 xff0c 不能在某些地方使用 xff0c 如kate xff0c 但有些地方可以使用 xff0c 如 chrome 使用 Ctrl 43 Space 时无法激活 Fcitx 1 检查你所输入的程序
  • IDM 激活

    打开 C WINDOWS system32 drivers etc 这个文件夹中有个 hosts 文件 大家用 记事本 打开 在最后一行加入 127 0 0 1 registeridm com 127 0 0 1 www registeri
  • Linux 挂载 exFat

    在Linux的学习中为方便大家学习了解更多的Linux方面的内容 xff0c 达内培训 技术小编本文将为大家详解关于Linux方面的技术内容 xff0c 提供给大家作为学习的参考 先说挂载exFAT格式的移动硬盘 xff0c 最近刚刚做了个
  • HTML中meta标签的作用

    HTML中meta标签的作用 首先 xff0c meta标签是一个自结束标签 xff0c 其格式为 lt meta gt xff0c 下面介绍meta标签的作用 xff1a 1 规定字符集 lt meta charset 61 34 utf
  • 多个浏览器窗口中间不同的Session

    关于多个窗口不同Session的作用就不说了 xff0c 直接说步骤 Firefox xff1a 编辑快捷方式的目标 xff0c 后面加上 p no remote Chrome xff1a 打开新的隐身窗口 IE xff1a 文件 gt 新
  • Nginx 开启对 PHP 的解析(转)

    安装 PHP 和 nginx 后 xff0c 无法解析 PHP 文件 其中 xff0c PHP 和 nginx 的编译安装 configure 如下 xff1a PHP 5 3 9 configure prefix 61 usr local
  • JDK 对应数字版本号

    J2SE 7 61 51 0x33 hex J2SE 6 0 61 50 0x32 hex J2SE 5 0 61 49 0x31 hex JDK 1 4 61 48 0x30 hex JDK 1 3 61 47 0x2F hex JDK
  • 打印控件

    Lodop
  • 二进制最大公约数算法

    求最大公约数的Euclid算法需要用到大量的取模运算 xff0c 这在大多数计算机上是一项复杂的工作 xff0c 相比之下减法运算 测试数的奇偶性 折半运算的执行速度都要更快些 二进制最大公约数算法避免了Euclid算法的取余数过程 二进制
  • 1、docker+k8s+kubesphere:准备(20200802更新免密)

    1 准备 docker 43 k8s 43 kubesphere准 环境准备 角色IP地址主机名docker版本硬件操作系统主192 168 5 151node151docker18 09 96核10GCentOS7 8节点192 168
  • 2、docker+k8s+kubesphere:docker安装

    2 docker安装 docker 43 k8s 43 kubesphere 卸载以前的docker yum remove y docker docker client docker client latest docker common
  • 7、docker+k8s+kubesphere:helm与tiller安装

    7 docker 43 k8s 43 kubesphere helm安装 官网地址 https kubesphere io docs zh CN installation install on k8s 官网虽然说低配置可以使用 xff0c
  • 8、docker+k8s+kubesphere:nfs安装(2020-08-02更新)

    8 docker 43 k8s 43 kubesphere nfs安装 server端安装在node151 yum y span class token function install span nfs utils rpcbind 配置文
  • 9、docker+k8s+kubesphere:Kubernetes安装(2020-08-02更新)

    9 docker 43 k8s 43 kubesphere Kubernetes安装 官网说明一定详细查看 span class token punctuation span 本文用的是2 1 1 span class token punc

随机推荐

  • Linux修改进程名称(setproctitle())

    每一个c程序都有个main函数 xff0c 作为程序启动入口函数 main函数的原型是int main int argc char argv 其中argc表示命令行参数的个数 xff1b argv是一个指针数组 xff0c 保存所有命令行字
  • 转载一篇文章:别人做毕业设计的思路

    发信人 zyzyis 小菜 十年一觉扬州梦 信区 SCU CS 标 题 我来谈谈毕业设计 xff08 准备篇 xff09 发信站 四川大学蓝色星空站 Sat Jan 15 11 15 53 2005 转信 很早就想写篇帖子来讲述自己做了近半
  • bash实现的回收站程序

    好久没写博了 哈哈 xff0c 最近在学习Linux 这是偶写的第一个shell脚本 xff0c 是一个实现类似windows里的回收站的程序 xff0c 可以避免误删文件 xff0c 希望能够对大家有所帮助 xff0c 当然自己练手是最重
  • 程序的格式

    一 格式 注 xff1a 比算法还重要 1 该注意的问题 xff1a xff08 1 xff09 大括号对齐 xff08 2 xff09 遇到 要缩进 xff1a Tab或Shift 43 Tab xff08 3 xff09 程序块之间加空
  • 解决中文版SUSELinux在远程终端上的乱码问题

    在远程桌面上打开Linux系统终端 xff0c 执行程序或某些命令时 xff0c 返回结果中出现乱码 解决方法 xff1a 计算机 gt YaST2控制中心 xff08 注 xff1a 非管理员用户 xff08 root xff09 进入时
  • 如何打开并读取QQ聊天记录Msg3.0.db文件的内容

    如何打开并读取QQ聊天记录Msg3 0 db文件的内容
  • 硬盘安装CentOS5.x笔记

    原来安装了一个64位的Linux xff0c 发现老是有些软件安装不了 xff0c 所以打算重新安装一个32位的 xff0c 选的CentOS5 2 i386版的Linux 镜像文件下载来后 xff0c 也懒得再刻盘 xff0c 打算从硬盘
  • 求一个unsigned int 数的二进制表示中有多少个1?

    第一种方法 xff0c 使用普通循环 unsigned int GetBitNum1 unsigned int nValue const unsigned int nNumOfBitInByte 61 8 unsigned int temp
  • UISlider 滑块控件—IOS开发

    声明 欢迎转载 xff0c 但是请尊重作者劳动成果 xff0c 转载请保留此框内声明 xff0c 谢谢 文章出处 xff1a http blog csdn net iukey PC上的滑块是很丑陋的 xff0c 因为我们只能通过鼠标去拖动他
  • sqlite3中BLOB数据类型存储大对象运用示例

    1 常用接口 个人比较喜欢sqlite 使用最方便 xff0c 唯一的准备工作是下载250K的源 xff1b 而且作者很热心 xff0c 有问必答 以下演示一下使用sqlite的步骤 xff0c 先创建一个数据库 xff0c 然后查询其中的
  • Linux进程同步之System V 信号量

    SystemV信号量是不属于 POSIX 标准 xff0c 它属于 SUS xff08 SingleUNIXSpecification xff09 单一规范中的扩展定义 它和 POSIX 信号量一样都提供基本的信号量功能操作 SystemV
  • IOS用CGContextRef画各种图形(文字、圆、直线、弧线、矩形、扇形、椭圆、三角形、圆角矩形、贝塞尔曲线、图片)...

    首先了解一下CGContextRef An opaque type that represents a Quartz 2D drawing environment Graphics Context是图形上下文 可以将其理解为一块画布 我们可
  • C++实现中文字频统计

    中文文本字频统计系统设计 一 实验内容 问题描述 中文信息处理中常用到汉字的字频统计 xff0c 设计一个小工具统计文本的字频 基本要求 1 输入一个中文文本 2 用三种排序分别输出结果 xff1a 按汉字出现顺序输出的字频 xff0c 按
  • [XMPP]我是怎么通过直接操作数据来为Openfire注册新用户的

    众所周知 xff0c Openfire的注册方式一般有三种 1 带内注册 In Band Registration 即客户端通过匿名方式与Openfire 服务器端建立连接并验证 xff0c 然后发起注册节点XML流 xff0c 以XMPP
  • IdeaVim插件使用技巧

    在 url 61 http kidneyball iteye com blog 1814028 IDEA Intellij小技巧和插件 url 一文中简单介绍了一下IdeaVim插件 在这里详细总结一下这个插件在日常编程中的一些常用小技巧
  • 机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾

    作者 xff1a 寒小阳 amp amp 龙心尘 时间 xff1a 2015年11月 出处 xff1a http blog csdn net han xiaoyang article details 49797143 声明 xff1a 版权
  • Android 获取系统设置参数。

    转载自 xff1a http blog 163 com fang wang2005 blog static 176928073201136105613638 如何获取Android系统设置参数 下面以获取时间格式为例 xff0c 来判断时间
  • linux下默认删除文件到回收站(bash实现)

    fedora下总是会把文件不小心删除了 xff0c 所以下面的脚本把实现 xff1a 文件删除默认移动到自己的回收站里面 功能 xff1a 脚本实现删除文件或者目录到 waste xff08 自己定义 xff09 脚本附带文件名或者目录名
  • android 开机启动程序

    做一个android开机就会自动启动的程序 xff0c 该程序只要启动一次 xff0c 以后开机就会自动启动 xff0c 直到删除该程序 android开机事件会发送一个叫做Android intent action BOOT COMPLE
  • 在 Linux 平台中调试 C/C++ 内存泄漏方法

    由于 C 和 C 43 43 程序中完全由程序员自主申请和释放内存 xff0c 稍不注意 xff0c 就会在系统中导入内存错误 同时 xff0c 内存错误往往非常严重 xff0c 一般会带来诸如系统崩溃 xff0c 内存耗尽这样严重的后果