C++-程序运行时如何进行内存泄露检测?

发布于 2016-11-13 02:26:50 字数 121 浏览 1381 评论 16

假设程序有10000行,如何在第5000行处找到程序当中已经出现的所有内存泄露?

PS:关键是找那些无法再找到的块,如:

int *p = new int;
p = new int;

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

灵芸 2017-09-21 12:34:17

这个需求其实就是垃圾回收啊,标记清除法之类的可以达到这种效果啊。
用户来实现的话需要用一个特殊的类来管理所有指针,同时记录下所有分配的内存。每个指针可以引用到其他指针或实际内存。一开始只有一个根指针,之后的每次分配和赋值操作,都改变其对指针和实际内存的引用。
回收时,从根指针扫描,不可达的内存块就是泄露的。
不过做成实时的效率应该很低。

瑾兮 2017-08-28 08:52:13

Visual Leak Detector是一款用于Visual C++的免费的内存泄露检测工具。可以在http://www.codeproject.com/tools/visualleakdetector.asp 下载到

瑾兮 2017-08-21 16:00:16

重载new和delete操作符,在重载中实现内存的检测

夜无邪 2017-08-19 07:06:31

楼主如果害怕内存泄漏的话,可以考虑使用智能指针,这个可以确保你申请的内存在不需要使用的时候被释放掉

浮生未歇 2017-07-24 16:07:12

dmalloc 多余的话不说 自己百度下 貌似php的源码 就是用这个做内存泄露检查的

dmalloc是一个简单易用的C/C++内存leak检查工具,以一个运行库的方式发布。

dmalloc能够检查出直到程序运行结束还没有释放的内存,并且能够精确指出在
哪个源文件的第几行。

dmalloc 主页: http://dmalloc.com

虐人心 2017-05-23 23:25:05

感覺樓要的不是簡單的檢測內存洩露,一般我們檢測內存洩露的方向都是定位哪塊內存洩露了,這塊內存是哪被創建出來的,一般是hook內存分配函數或自建內存管理機制來跟蹤。但樓主要的貌似是運行期檢測出某塊內存開始洩露了(比如在5000行處內存p開始洩露了)。如果要實現這樣的機制,需要有類似GC的方式,對每塊分配的內存的使用進行跟蹤,這樣才能知道它在哪個時候沒人使用它了,又沒人將它銷毀掉。
個人想法可以使用類似智能指針的方式,一般智能指針我們是用來智能銷毀內存,但其實我們改造一下就可以用來跟蹤某塊內存是否有人在使用。

甜柠檬 2017-05-17 00:45:32

泄露检测和垃圾回收是一起的,LZ所说的内存泄露显然是废指针,也就是类似java中GC(垃圾回收)。
比较方便的实现你可以看下c++11的智能指针。
http://blog.csdn.net/hackbuteer1/article/details/7561235
大概的意思就是用一个指针对象来管理所有的指针(也就是int*,包括创建、删除、赋值、取地址什么的)这样所有的对于指针的操作就都是可控的了,然后你只要记录这个指针被引用了多少次(这是个简单粗暴的实现,实际是会有问题的,比如循环引用之类的,但是要是复杂的实现也不是一句两句能说清楚了)每次引用+1,指针被销毁就-1。指针=0我们就简单的说这个内存泄露了(在上面的文章里就顺手delete掉了)。

夜无邪 2017-03-24 20:40:24

可以使用boost来尽量避免这些问题

偏爱自由 2017-03-19 10:12:20

严格意义上来说是不可能的,C++对内存的操作太自由了,我有可能把指针转成了整数、将来再转回来释放,有可能把指针保存成了和另一个指针的差值,所以没有实际的办法知道当前的内存到底有没有实际被记录在某个地方。

如果限定不允许将指针强制转化为数值之后存储,而且可以忍受使用一些特殊的方法来定义指针的话,可以考虑这样的方法:

在Debug版重载new和delete operator,额外分配一个int的空间,用来存储引用计数,同时(可选地)在特定位置保存所有new生成的地址列表,delete时清除;Release版使用默认的new和delete
使用宏来定义指针,在Debug中将指针变成一个对象,在对象构造、析构的时候修改引用计数

 #ifdef _DEBUG
void *operator new(size_t size)
{
void *p = malloc(size + 4);
*(int*)p = 0;
`return p` + 4;
}
void operator delete(void *p)
{
return free(p - 4);
}
#endif

class Pointer
{
//实现基类功能
...
};

//强类型指针
template<class T>
class TypedPointer : public Pointer
{
...
};

template<>
class TypedPointer<void> : public Pointer
{
...
};

#ifdef _DEBUG
#define POINTER(type) TypedPointer<type>
#else
#define POINTER(type) type*
#endif

具体怎么实现我想还是很容易想到的吧。
测试时只要在需要的时候,读取当前所有分配列表,然后逐个检查引用计数,如果有引用计数为0的,就说明发生了内存泄露。调试完成后切换回Release会自动回到正常使用指针的情况。

和垃圾回收时的情形一样,引用计数并不能保证查出所有的内存泄露,如果有相互引用的情况就会出现问题。

瑾兮 2017-03-14 07:05:33

用钩子勾住 new 和 delete ,new的时候打印地址和大小,删除也打印地址和大小,如果没有new和delete,就意味着内存泄漏了

虐人心 2017-02-22 05:27:08

快到悬赏期啦,我一直有关注这个问题的动态,不过还没发现解决这个问题的一个比较好的方案,所以悬赏得分这一块,让系统管理员来辅助给吧,但是希望这个问题的讨论不会间断。

我先介绍下这个问题的背景吧,这道题目是我今年找工作的时候遇到的一道面试题,我觉得很不错,我就发了上来。当时面试官问我的时候,我想了蛮长时间,没有想出这个问题,于是就请教了面试官,这个问题的解法。

当时面试官给我的解答是:你可以使用引用计数类智能指针对象,不过这个引用计数类智能指针对象比较特殊,他说当在析构函数中判断引用计数为0的时候,我并不真正析构他,而是报这块内存泄露的错误。但是我后来发现这个解答不妥。因为如果这样的话会有很多问题。
假如我现在的智能指针类是SmartPointer;

 SmartPointer oldObject(new int);
SmartPointer newObject(new int);
oldObject = newObject;

就最后这个赋值操作,他都会报内存泄露,而这个完全颠覆了自己对内存泄露的看法啦。。。因为这样的话 所以的赋值操作都会内存泄露。。。这我不能接受。

下面是我自己后来想的一个方法,朋友们可以探讨下。
如题目中所说,要在第5000行检测。
那我做这么一个操作,我自己最开始定义一个静态数组array。然后我将每次申请内存块获得的指针对他进行哈希,做一次哈希映射,将其存在array中,然后每次删除内存块的时候,我又到这个哈希数组中将这个指针删除掉。注意冲突的地方,我会用链表的形式将其链起来。

然后这样的话,我在第5000行的时候,会有2个东西,一个是我自己的哈希映射的静态数组array,还有一个是系统为我们维护的一个调试堆内存块链表(可以通过分配一个字节的内存块,获得这个调试堆内存块链表的头指针)。然后我扫描整个调试堆内存块链表,将其中的每个地址都做hash映射,并到array中相应的位置去找,如果在array中没有查找成功,我就认为这个内存块泄露了(特指题目中所说的“野内存块”)。

不对之处,请各位指正。

清晨说ぺ晚安 2017-02-13 03:28:46

这个应该可以通过重载new和delete运算符得到把,维护一个全局的链表,new的时候在链表中加入一项(记录行号和对象名),delete的时候删除该项,然后在第5000的行的时候遍历这个链表就可以了

泛泛之交 2017-02-03 14:43:09

所有的变量在分配之后,都会维护一个链表,用来保存相关的信息,每个链表的节点是一个_CrtMemBlockHeader结构,这个在DBGINT.H中有定义

typedef struct _CrtMemBlockHeader
{
// Pointer to the block allocated just before this one:
   struct _CrtMemBlockHeader *pBlockHeaderNext;
// Pointer to the block allocated just after this one:
   struct _CrtMemBlockHeader *pBlockHeaderPrev;
   char *szFileName;    // File name
   int nLine;                  // Line number
   size_t nDataSize;      // Size of user block
   int nBlockUse;         // Type of block
   long lRequest;          // Allocation number
// Buffer just before (lower than) the user's memory:
   unsigned char gap[nNoMansLandSize];
} _CrtMemBlockHeader;

从中可以看到,这个结构会记录分配内存的相关信息,包括前面分配后面分配的块,和文件的名称以及文件所在的行数等等一些信息,我们所申请的内存会紧跟在这个内存之后。需要注意的是,这个结构仅当运行debug版时才会有,运行release时是不会分配的。但是有一个问题是这个链表并没有提供一个接口去访问他,但是我们可以通过这样去得到

char *p= new char;
 _CrtMemBlockHeader *pStartPos = pHdr(pheap);

当然要保证这个是在程序开始时,即所有动态申请空间的操作之前去获得,然后得到的就是当前你申请的空间的位置,然后使用pStartPos->pBlockHeaderNext;即可访问你真正要维护的链表开始节点了,其中关于pHdr的定义为

#define pbData(pblock) ((unsigned char *)((_CrtMemBlockHeader *)pblock + 1))
#define pHdr(pbData) (((_CrtMemBlockHeader *)pbData)-1)

这样就可以获得维护的链表,从而可以判断内存是否释放以及相关信息
接着就是怎么去释放了,由于对象析构的顺序是和申请相反的,你可以定义一个自己的内存管理类的全局变量(全局变量是在编译期就确定存储位置的)。用这个对象去获取刚刚获取的链表位置信息,这样在你管理类的析构函数里面去负责释放未释放的内存或者弹出错误信息等等。就不说的太细了。。

我想有个问题你理解错了(如果按我对你说的理解的话,我理解的不清楚也还请指正,我就先说说我的想法),并不是说我在第5000行有内存分配,我就在这行上面写一个我自己的分配一个字节的操作。而是在程序开始的时候,当然了,不能保证在所有操作之前,这里就说是在程序的入口函数里面吧。去获取维护的链表的头指针,因为在入口函数之前,是不会分配的堆中内存的,编译阶段确定的内存地址都是存在栈中的,相信这个不必赘述。而这个管理类全局对象,也是存在栈中的,也就保证了比动态分配的那些变量构造的更早,所以析构的更迟。这样你得到的链表,除了头指针意外,其他的都是维护程序实际分配的内存信息,这样在析构函数里面可以对这些信息进行操作。
针对你说的这块内存到底是被析构了还是直接被覆盖了(也就是内存泄露)。可以看一下调用delete操作的时候,系统会干些什么

void operator delete(void * pUserData)
{
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if ((pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK);
    
    _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));//在这里,如果该段内存已经被释放,若果在释放一次的话这里就会报错。
    
    ......
}

可以看到系统在释放内存的时候会先检测该段内存有没有被释放,若是释放了就会报错。所以可以参考一下宏_BLOCK_TYPE_IS_VALID中的操作,根据返回的错误信息判断该段内存是已经释放还是内存泄露(也就是到了最后还是没有释放)。

归属感 2017-01-20 19:19:00

http://www.codeproject.com/Articles/393957/Cplusplus-Memory-Leak-Finder
看看这篇文章,或许对你有帮助。
"Regardless of this, I hope the article has provided a certain amount of insight into how leaks can be spotted in a non-intrusive way(非侵入式) (in the sense that the application code does not need to be instrumented or otherwise augmented).
And weirdly enough, the Code Project article submission wizard does not allow me to upload .tar files, so if you want one of those instead; you can get it from here (the allocation_info.cpp file is broken but I'll fix that at a later time, the .zip file is correct); https://sites.google.com/site/fredrikbornander/Home/leakfinder.tar?attredirects=0&d=1
"
源码下载链接,C++ Memory Leak Finder

想挽留 2016-11-29 00:40:18

我这个方法对源程序改的挺多。主要是使用智能指针,不知道符不符合lz的要求。
智能指针的引用计数放在自定义的类中,所以需要将所有的原始类型进行封装,下面是所有自定义类(不包括只能指针类)的基类。

 class Base{
public:
int getCount(){return count;}
Base() : count(1){}
~Base(){}
void ref() {
count++;
}
bool deref() {
if(count == 1)
return true;
count--;
return false;
}
private:
int count;
};

下面一个我封装的int类型的自定义类。

 class Integer : public Base{
public:
Integer(int i) : m(i) {}
~Integer(){
}
int getInt() {return m;}
int setInt(int i) { m = i;}

private:
int m;
};

下面给出智能指针的类,这里重载了两个赋值运算符,当当前指针所指向内存的引用计数为1,且将要指向其他内存的时候,就提示内存泄漏。发生内存泄漏后,这里只是提示一下,然后什么都没做,其实可以直接assert(0)。

 template<typename T> class Ptr{
public:
Ptr() : p(NULL) {}
Ptr(T* a) : p(a) {}
Ptr(const Ptr& ptr){
p = ptr.p;
if(p != NULL)
p -> ref();
}
~Ptr() {
if(p != NULL && p->deref())
delete p;
}
Ptr& operator= (T* pt){
if(p == pt)
return *this;
if(p != NULL && p->getCount() == 1){
cout << "memory leak " << endl;
return *this;
}
pt->ref();
if(p != NULL)
p->deref();
p = pt;
return *this;
}
Ptr& operator= (const Ptr<T>& ptr){
if(ptr.p == p)
return *this;
if(p != NULL && p->getCount() == 1){
cout << "memory leak " << endl;
return *this;
}

if(ptr.p != NULL)
ptr.p->ref();
if(p != NULL)
p->deref();
p = ptr.p;

return *this;
}

T* p;
};

下面是一个简单的测试。首先将lz给的那段代码重写后测试。

 Ptr<Integer> a(new Integer(1));
a = new Integer(2);

以上代码会提示发生了内存泄漏。但没想到好办法打出发生内存泄漏对应的行号。
下面的是在a指向其他内存之前,用b指向a的内存,保证不发生泄漏。

 Ptr<Integer> a(new Integer(1));
Ptr<Integer> b = a;
a = new Integer(2);

这样就没有内存泄漏的提示

甜柠檬 2016-11-14 13:55:04

_CrtDumpMemoryLinks()函数可以检测内存泄露

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文