VC内存泄露检查工具:Visual Leak Detector

www.diybl.com 时间:2009-04-12 作者:匿名 编辑:sky 

 

 

初识Visual Leak Detector
       灵活自由是C/C++语言的一大特色,而这也为C/C++程序员出了一个难题。当程序越来越复杂时,内存的管理也会变得越加复杂,稍有不慎就会出现内存问 题。内存泄漏是最常见的内存问题之一。内存泄漏如果不是很严重,在短时间内对程序不会有太大的影响,这也使得内存泄漏问题有很强的隐蔽性,不容易被发现。 然而不管内存泄漏多么轻微,当程序长时间运行时,其破坏力是惊人的,从性能下降到内存耗尽,甚至会影响到其他程序的正常运行。另外内存问题的一个共同特点 是,内存问题本身并不会有很明显的现象,当有异常现象出现时已时过境迁,其现场已非出现问题时的现场了,这给调试内存问题带来了很大的难度。
      
       Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具。可以在http://www.codeproject.com/tools/visualleakdetector.asp 下载到。相比较其它的内存泄露检测工具,它在检测到内存泄漏的同时,还具有如下特点:
1、 可以得到内存泄漏点的调用堆栈,如果可以的话,还可以得到其所在文件及行号;
2、 可以得到泄露内存的完整数据;
3、 可以设置内存泄露报告的级别;
4、 它是一个已经打包的lib,使用时无须编译它的源代码。而对于使用者自己的代码,也只需要做很小的改动;
5、 他的源代码使用GNU许可发布,并有详尽的文档及注释。对于想深入了解堆内存管理的读者,是一个不错的选择。
      
       可见,从使用角度来讲,Visual Leak Detector简单易用,对于使用者自己的代码,唯一的修改是#include Visual Leak Detector的头文件后正常运行自己的程序,就可以发现内存问题。从研究的角度来讲,如果深入Visual Leak Detector源代码,可以学习到堆内存分配与释放的原理、内存泄漏检测的原理及内存操作的常用技巧等。
       本文首先将介绍Visual Leak Detector的使用方法与步骤,然后再和读者一起初步的研究Visual Leak Detector的源代码,去了解Visual Leak Detector的工作原理。
使用Visual Leak Detector(1.0)
       下面让我们来介绍如何使用这个小巧的工具。
       首先从网站上下载zip包,解压之后得到vld.h, vldapi.h, vld.lib, vldmt.lib, vldmtdll.lib, dbghelp.dll等文件。将.h文件拷贝到Visual C++的默认include目录下,将.lib文件拷贝到Visual C++的默认lib目录下,便安装完成了。因为版本问题,如果使用windows 2000或者以前的版本,需要将dbghelp.dll拷贝到你的程序的运行目录下,或其他可以引用到的目录。
       接下来需要将其加入到自己的代码中。方法很简单,只要在包含入口函数的.cpp文件中包含vld.h就可以。如果这个cpp文件包含了stdafx.h,则将包含vld.h的语句放在stdafx.h的包含语句之后,否则放在最前面。如下是一个示例程序:
#include <vld.h>
void main()
{

}
       接下来让我们来演示如何使用Visual Leak Detector检测内存泄漏。下面是一个简单的程序,用new分配了一个int大小的堆内存,并没有释放。其申请的内存地址用printf输出到屏幕上。
#include <vld.h>
#include <stdlib.h>
#include <stdio.h>
 
void f()
{
    int *p = new int(0x12345678);
    printf("p=%08x, ", p);
}
 
void main()
{
    f();
}
编译运行后,在标准输出窗口得到:
p=003a89c0
 
在Visual C++的Output窗口得到:
 
WARNING: Visual Leak Detector detected memory leaks!
---------- Block 57 at 0x003A89C0: 4 bytes ---------- --57号块0x003A89C0地址泄漏了4个字节
 Call Stack:                                               --下面是调用堆栈
    d:/test/testvldconsole/testvldconsole/main.cpp (7): f --表示在main.cpp第7行的f()函数
    d:/test/testvldconsole/testvldconsole/main.cpp (14): main –双击以引导至对应代码处
    f:/rtm/vctools/crt_bld/self_x86/crt/src/crtexe.c (586): __tmainCRTStartup
    f:/rtm/vctools/crt_bld/self_x86/crt/src/crtexe.c (403): mainCRTStartup
    0x7C816D4F (File and line number not available): RegisterWaitForInputIdle
 Data:                                   --这是泄漏内存的内容,0x12345678
    78 56 34 12                                                  xV4..... ........
 
Visual Leak Detector detected 1 memory leak.   
第二行表示57号块有4字节的内存泄漏,地址为0x003A89C0,根据程序控制台的输出,可以知道,该地址为指针p。程序的第7行,f()函数里,在该地址处分配了4字节的堆内存空间,并赋值为0x12345678,这样在报告中,我们看到了这4字节同样的内容。
可以看出,对于每一个内存泄漏,这个报告列出了它的泄漏点、长度、分配该内存时的调用堆栈、和泄露内存的内容(分别以16进制和文本格式列出)。双击该堆栈报告的某一行,会自动在代码编辑器中跳到其所指文件的对应行。这些信息对于我们查找内存泄露将有很大的帮助。
这 是一个很方便易用的工具,安装后每次使用时,仅仅需要将它头文件包含进来重新build就可以。而且,该工具仅在build Debug版的时候会连接到你的程序中,如果build Release版,该工具不会对你的程序产生任何性能等方面影响。所以尽可以将其头文件一直包含在你的源代码中。
Visual Leak Detector工作原理
       下面让我们来看一下该工具的工作原理。
       在这之前,我们先来看一下Visual C++内置的内存泄漏检测工具是如何工作的。Visual C++内置的工具CRT Debug Heap工作原来很简单。在使用Debug版的malloc分配内存时,malloc会在内存块的头中记录分配该内存的文件名及行号。当程序退出时CRT 会在main()函数返回之后做一些清理工作,这个时候来检查调试堆内存,如果仍然有内存没有被释放,则一定是存在内存泄漏。从这些没有被释放的内存块的 头中,就可以获得文件名及行号。
       这种静态的方法可以检测出内存泄漏及其泄漏点的文件名和行号,但是并不知道泄漏究竟是如何发生的,并不知道该内存分配语句是如何被执行到的。要想了解这 些,就必须要对程序的内存分配过程进行动态跟踪。Visual Leak Detector就是这样做的。它在每次内存分配时将其上下文记录下来,当程序退出时,对于检测到的内存泄漏,查找其记录下来的上下文信息,并将其转换成 报告输出。
      
初始化
       Visual Leak Detector要记录每一次的内存分配,而它是如何监视内存分配的呢?Windows提供了分配钩子(allocation hooks)来监视调试堆内存的分配。它是一个用户定义的回调函数,在每次从调试堆分配内存之前被调用。在初始化时,Visual Leak Detector使用_CrtSetAllocHook注册这个钩子函数,这样就可以监视从此之后所有的堆内存分配了。
       如何保证在Visual Leak Detector初始化之前没有堆内存分配呢?全局变量是在程序启动时就初始化的,如果将Visual Leak Detector作为一个全局变量,就可以随程序一起启动。但是C/C++并没有约定全局变量之间的初始化顺序,如果其它全局变量的构造函数中有堆内存分 配,则可能无法检测到。Visual Leak Detector使用了C/C++提供的#pragma init_seg来在某种程度上减少其它全局变量在其之前初始化的概率。根据#pragma init_seg的定义,全局变量的初始化分三个阶段:首先是compiler段,一般c语言的运行时库在这个时候初始化;然后是lib段,一般用于第三 方的类库的初始化等;最后是user段,大部分的初始化都在这个阶段进行。Visual Leak Detector将其初始化设置在compiler段,从而使得它在绝大多数全局变量和几乎所有的用户定义的全局变量之前初始化。
 
记录内存分配
       一个分配钩子函数需要具有如下的形式:
int YourAllocHook( int allocType, void *userData, size_t size, int blockType, long requestNumber, const unsigned char *filename, int lineNumber);
       就像前面说的,它在Visual Leak Detector初始化时被注册,每次从调试堆分配内存之前被调用。这个函数需要处理的事情是记录下此时的调用堆栈和此次堆内存分配的唯一标识——requestNumber。
       得到当前的堆栈的二进制表示并不是一件很复杂的事情,但是因为不同体系结构、不同编译器、不同的函数调用约定所产生的堆栈内容略有不同,要解释堆栈并得到 整个函数调用过程略显复杂。不过windows提供一个StackWalk64函数,可以获得堆栈的内容。StackWalk64的声明如下:
BOOL StackWalk64(
 DWORD MachineType,
 HANDLE hProcess,
 HANDLE hThread,
 LPSTACKFRAME64 StackFrame,
 PVOID ContextRecord,
 PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
 PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
 PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
 PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
);
STACKFRAME64结构表示了堆栈中的一个frame。给出初始的STACKFRAME64,反复调用该函数,便可以得到内存分配点的调用堆栈了。
    // Walk the stack.
    while (count < _VLD_maxtraceframes) {
        count++;
        if (!pStackWalk64(architecture, m_process, m_thread, &frame, &context,
                          NULL, pSymFunctionTableAccess64, pSymGetModuleBase64, NULL)) {
            // Couldn't trace back through any more frames.
            break;
        }
        if (frame.AddrFrame.Offset == 0) {
            // End of stack.
            break;
        }

 

转载于:https://www.cnblogs.com/secbook/archive/2009/10/29/2655440.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/268388.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

结对-人机对战象棋游戏-开发过程

我们实现了框架的规范把框架放在网页的居中位置&#xff0c;width325 height402.的一个框架 转载于:https://www.cnblogs.com/lzy616/p/7560408.html

服务器的虚拟主机用途,服务器的虚拟主机用途

服务器的虚拟主机用途 内容精选换一换为了对源端服务器进行迁移可行性评估以及为后续目的端服务器的选择和配置提供必要性数据&#xff0c;迁移Agent会收集源端服务器的相关信息并上报到主机迁移服务。收集的Windows操作系统的具体数据如表1所示。收集的Linux操作系统的具体数据…

2台无线路由器互连

2台TP-LINK无线路由器办公室电脑无法上网通过连接前台路由器实现上网前台用1台路由器有线接2台电脑办公室用1台路由器分接3台电脑由于位置关系和房屋结构不利于走网线所以用2台无线路由器实现互联操作的大体步骤1、由浏览器进入路由器WEB控制界面2、关闭路由器DHCP功能3、在无线…

java8使用filter(Objects::nonNull) 过滤null

list.stream().filter(Objects::nonNull).collect(Collectors.toList());

vi常用快捷键

1)移动光标h &#xff1a;光标左移一个字符j &#xff1a;光标上移一个字符k &#xff1a;光标下移一个字符l &#xff1a;光标右移一个字符0 &#xff1a;光标移至行首$ &#xff1a;光标移至行尾H &#xff1a;光标移至屏幕首行M &#xff1a;光标移至屏幕中间L &#xff1a;…

华为帐号忘记显示服务器繁忙,显示云服务器繁忙怎么操作

显示云服务器繁忙怎么操作 内容精选换一换在您申请了云耀云服务器后&#xff0c;可以通过管理控制台查看和管理您的云耀云服务器。本节介绍如何查看云耀云服务器的详细配置&#xff0c;包括云耀云服务器名称、镜像信息、系统盘、数据盘、安全组、弹性公网IP等信息。登录管理控制…

C#令人迷惑的DateTime:世界标准时间还是本地时间?

先来看一段代码&#xff1a; 复制内容到剪贴板程序代码DateTime time DateTime.Parse("2013-07-05 00:00:00");Console.WriteLine(time.ToUniversalTime()); //2013/7/4 16:00:00Console.WriteLine(time.ToLocalTime()); //2013/7/5 8:00:00这让人搞不清楚DateTime的…

集合删除元素技巧 removeIf

集合删除元素技巧 removeIf removeIf() 是从 JDK1.8 开始提供的。 之前我们删除 List 中的元素的话&#xff0c;一般使用循环遍历实现。今天发现 removeIf 很好用&#xff0c;记录一下。

话里话外:猎人讲小事——兔子、凤凰与猎狗

一只兔子在森林里迷了路&#xff0c;去请教一只凤凰&#xff0c;凤凰告诉他&#xff0c;一直向东方走就可以了&#xff0c;然后还送给了兔子一个指南针&#xff0c;并教会兔子如何使用。兔子生性胆小&#xff0c;又花了100元雇了个猎狗带路。一兔一狗拿着指南针&#xff0c;向东…

Android弹出Dialog使用举例

Android详细的对话框AlertDialog.Builder使用方法 7种形式的Android Dialog使用举例 第30章、常见对话框之一AlertDialog&#xff08;从零开始学Android&#xff09; 转载于:https://www.cnblogs.com/defineconst/p/7574458.html

LAMP搭建之三:php编译安装

1、下载php的稳定源代码包。我这里下载的是php-5.2.3。php官网从google一搜就找到了。2、解压tar jxvf php-5.2.3.tar.bz2 -C /usr/src/cd /usr/src/php-5.2.3/3、配置程序./configure --prefix/usr/local/php --with-apxs2/usr/local/apache2/bin/apxs --with-config-file-pat…

Mysql查看慢查询日志是否开启 show VARIABLES like ‘slow_query%‘;

慢查询日志 在讲读操作变慢的原因之前我们先来看看是如何定位慢 SQL 的。Mysql 中有一个叫作慢查询日志的东西&#xff0c;它是用来记录超过指定时间的 SQL 语句的。默认情况下是关闭的&#xff0c;通过手动配置才能开启慢查询日志进行定位。 具体的配置方式是这样的&#xf…

应用程序 /dev/rtc 编程 获取时间 2011-12-13 01:01:06【转】

本文转载自&#xff1a;http://blog.chinaunix.net/uid-16785183-id-3040310.html 分类&#xff1a; 原文地址&#xff1a;应用程序 /dev/rtc 编程 获取时间 作者&#xff1a;yuweixian4230 找的一些rtc资料&#xff1a;系统时钟硬件与LINUX时间表示二 之 Linux内核对RTC的编…

GIPS 详细介绍

转自&#xff1a;百度百科 Global IP Sound(GIPS)协议&#xff0c;著名的语音聊天工具skype所使用的协议   Global IP Solutions (GIPS) 的前身为Global IP Sound (GIPS)&#xff0c;专为数据包网络的实时通信应用市场&#xff0c;开发行业领先的嵌入式媒体处理…

重置MYSQL密码后,Navicat连接报错:2003 - Can‘t connect to MySQL server on ‘127.0.0.1‘ (61 “Connection refused“)

重置MYSQL密码后&#xff0c;Navicat连接报错&#xff1a;2003 - Can‘t connect to MySQL server on ‘127.0.0.1‘ (61 “Connection refused“) 报错如下 解决方法&#xff1a;勾选使用套接字文件

一个小栗子聊聊JAVA泛型基础

背景 周五本该是愉快的&#xff0c;可是今天花了一个早上查问题&#xff0c;为什么要花一个早上&#xff1f;我把原因总结为两点&#xff1a; 日志信息严重丢失&#xff0c;茫茫代码毫无头绪。对泛型的认识不够&#xff0c;导致代码出现了BUG。第一个原因可以通过以后编码谨慎的…

《WCF技术内幕》翻译25:第2部分_第5章_消息:创建一个消息(下)之MessageFault

Message和SOAP Fault老徐备注1 Message类型定义了一些用来创建表示SOAP Fault消息对象的工厂方法。SOAP Fault是SOAP消息的一种形式&#xff0c;它用来表示错误信息。在SOAP规范&#xff08;1.1 和1.2&#xff09;对于消息体内容&#xff0c;并且某些时候&#xff0c;关于SOAP消…

mac解决mysql忘记密码的问题(亲测有效)

打开终端依次执行如下命令 # 第一步&#xff0c;进入mysql服务 sudo /usr/local/mysql/support-files/mysql.server stop# 第一步&#xff0c;进入mysql的bin目录 cd /usr/local/mysql/bin/# 第二步&#xff0c;使用root账户&#xff08;这一步要输入mac密码&#xff09; sudo…

UVA - 572 Oil Deposits

/*1. 图也有DFS遍历和BFS遍历&#xff0c;前者用递归实现&#xff0c;后者用队列实现。由于DFS更容易编写&#xff0c;一般用DFS求连通块。求多维数组连通块的过程也称为种子填充2. 一般要尽量避免同一个格子被访问了两次&#xff0c;但是在这道题中&#xff0c;即便访问两次&a…

程序员笑话几则

自行车一个程序员骑着一个很漂亮的自行车到了公司&#xff0c;另一个程序员看到了他&#xff0c;问到&#xff0c;“你是从哪搞到的这么漂亮的车的&#xff1f;”骑车的那个程序员说&#xff0c;“我刚从那边过来&#xff0c;有一个漂亮的姑娘骑着这个车过来&#xff0c;并停在…