window使用内存泄漏检测工具Leakdiag记录

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/jianjian1992/article/details/51458527

Leakdiag安装

windows下内存泄漏查找工具Leakdiag使用起来还是蛮方便的。
Leakdiag下载之后按照默认设置安装,否则好像是会出问题的。
默认安装是直接装在C盘下。

那它是如何来检测内存泄漏的呢?
下图所示是Leakdiag目录,Logs文件夹里边存储的就是记录的日志,我们则是通过启动leakdiag应用程序进行检测。
这里写图片描述

第一次试验

首先测试一下常见的new和malloc作为练手。
在vs里边写了以下程序:

void testNew()
{
    char *data = new char[100];
    data[0] = 'a';
    cout << data[0];
}
void testNewWithDelete()
{
    char *data1 = new char[100];
    data1[0] = 'b';
    cout << data1[0];
    delete[] data1;
    data1 = NULL;
}
void testMalloc()
{
    int *data2 = (int *)malloc(sizeof(int)*100);
    data2[0] = 1;
    cout << data2[0];
}
void testMallocWithFree()
{
    int *data3 = (int *)malloc(sizeof(int)*100);
    data3[0] = 2;
    cout << data3[0];
    free(data3);
    data3 = NULL;
}
int main()
{
    for (int i = 0; i < 100000; i++)
    {
        testNew();
        testNewWithDelete();
        testMalloc();
        testMallocWithFree();
    }
    return 0;
}

我的工程名字叫memoryTest,然后启动调试,会出现memoryTest.exe。
然后启动Leakdiag应用程序,出现下图,我们首先在application里边找到memoryTest.exe,然后在Memory allocators这里选择Heap Allocator,因为内存泄漏主要是因为程序员申请了空间忘记释放,这些内存空间都是在堆区申请,所以查看堆区就好了,然后按下start。
这里写图片描述
之后到这一幅图,我们点击Log按钮,它就开始记录了,觉得程序已经把我们要检测的部分都跑好了就可以按stop,至此操作结束。
这里写图片描述

查看Log记录

在Logs目录里边出现了刚刚记录的xml文件,我是用Notepad++来打开xml文件的。
这里写图片描述
打开xml文件以后,内容好多哦,不过我们只需要关心有
dll=”memoryTest.exe”的部分。
下面按照顺序(在新标签页打开图片会显示更加清楚):
这里写图片描述

第一个是

<FRAME num="0" dll="MSVCR100.dll" function ="malloc" offset="0x36" filename="" line="" addr="0x713B0269" />
<FRAME num="1" dll="memoryTest.exe" function ="main" offset="0x5B" filename="e:\vsworkspace\memorytest\memorytest\test.cpp" line="81" addr="0x10610BB" />
<FRAME num="2" dll="memoryTest.exe" function ="__tmainCRTStartup" offset="0x122" filename="f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c" line="555" addr="0x1062211" />

首先观察第一个,从下往上看,首先看frame num=2的这一行,提示函数是__tmainCRTStartup,不管它。
然后是frame num = 1的这一行,提示函数是main,说明问题出在main函数里边,接着又line = “81”,说明问题出在第81行。
然后再往上一行,frame num = 0这一行,提示函数是malloc,说明是malloc带来的问题。

第二个:

<FRAME num="0" dll="MSVCR100.dll" function ="malloc" offset="0x36" filename="" line="" addr="0x713B0269" />
<FRAME num="1" dll="MSVCR100.dll" function ="operator new" offset="0x10" filename="" line="" addr="0x713B233B" />
<FRAME num="2" dll="memoryTest.exe" function ="main" offset="0x27" filename="e:\vsworkspace\memorytest\memorytest\test.cpp" line="79" addr="0x1061087" />

这一段便是说在main的79行里边使用了new,而在frame=0这里居然有malloc,说明new的实现也是依靠了malloc哦!
我们回过头来看看main函数,出问题的地方在79行和81行,正好对了是吧!
这里写图片描述

使用注意

上面示例正确地指出了出现问题的地方,但是使用不当就会出问题的。

代码顺序

首先是代码上的问题,我是按照以下顺序写代码的。
1.申请空间
2.对空间进行赋值
3.使用空间中的某个值
4.释放空间(如果有的话)

经过实验发现如果去掉了第3个步骤,也就是使用空间的值,在我这里便是cout,那么结果就会出问题,不论有没有步骤2,最终结果都如下,提示在80和82行出了问题, ( ´◔ ‸◔`),这样找出的结果就完全搓掉了呢,所以一定要使用了未释放的空间,它才能正确找到位置!赋值不算是使用哦!

<FRAME num="0" dll="MSVCR100.dll" function ="malloc" offset="0x36" filename="" line="" addr="0x71350269" />
<FRAME num="1" dll="MSVCR100.dll" function ="operator new" offset="0x10" filename="" line="" addr="0x7135233B" />
<FRAME num="2" dll="memoryTest.exe" function ="main" offset="0x27" filename="e:\vsworkspace\memorytest\memorytest\test.cpp" line="80" addr="0x361087" />


<FRAME num="0" dll="MSVCR100.dll" function ="malloc" offset="0x36" filename="" line="" addr="0x71350269" />
<FRAME num="1" dll="memoryTest.exe" function ="main" offset="0x3E" filename="e:\vsworkspace\memorytest\memorytest\test.cpp" line="82" addr="0x36109E" />
<FRAME num="2" dll="memoryTest.exe" function ="__tmainCRTStartup" offset="0x122" filename="f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c" line="555" addr="0x361FD1" />

函数里边or直接放在main里边

我在示例里边都是将测试代码放在函数里边,在main函数里边对这些函数进行调用。如果我直接将这些代码放到main函数里边呢?
代码如下:

int main()
{
    for (int i = 0; i < 10000000; i++)
    {   
        char *data = new char[100];
        //data[0] = 'a';
        cout << data[0];
        char *data1 = new char[100];
        //data1[0] = 'b';
        cout << data1[0];
        delete[] data1;
        data1 = NULL;

        int *data2 = (int *)malloc(sizeof(int)*100);
        //data2[0] = 1;
        cout << data2[0];
        int *data3 = (int *)malloc(sizeof(int)*100);
        //data3[0] = 2;
        cout << data3[0];
        free(data3);
        data3 = NULL;
    }
    return 0;
}

得到的记录如下:

<FRAME num="0" dll="MSVCR100.dll" function ="malloc" offset="0x36" filename="" line="" addr="0x71350269" />
<FRAME num="1" dll="memoryTest.exe" function ="main" offset="0x62" filename="e:\vsworkspace\memorytest\memorytest\test.cpp" line="90" addr="0xE910C2" />
<FRAME num="2" dll="memoryTest.exe" function ="__tmainCRTStartup" offset="0x122" filename="f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c" line="555" addr="0xE92211" />
<FRAME num="3" dll="kernel32.dll" function ="BaseThreadInitThunk" offset="0x12" filename="" line="" addr="0x760E1174" />
<FRAME num="4" dll="ntdll.dll" function ="RtlInitializeExceptionChain" offset="0x63" filename="" line="" addr="0x77A7B3F5" />
<STACKID>003B42A8</STACKID>
</STACK>
<STACK numallocs="0753" size="0100" totalsize="075300">
<STACKSTATS>
<SIZESTAT size="0100" numallocs="0753"/>
<HEAPSTAT handle="460000" numallocs="0753"/>
</STACKSTATS>
<FRAME num="0" dll="MSVCR100.dll" function ="malloc" offset="0x36" filename="" line="" addr="0x71350269" />
<FRAME num="1" dll="MSVCR100.dll" function ="operator new" offset="0x10" filename="" line="" addr="0x7135233B" />
<FRAME num="2" dll="memoryTest.exe" function ="main" offset="0x27" filename="e:\vsworkspace\memorytest\memorytest\test.cpp" line="81" addr="0xE91087" />
<FRAME num="3" dll="memoryTest.exe" function ="__tmainCRTStartup" offset="0x122" filename="f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c" line="555" addr="0xE92211" />
<FRAME num="4" dll="kernel32.dll" function ="BaseThreadInitThunk" offset="0x12" filename="" line="" addr="0x760E1174" />

记录显示在第90行出现malloc问题,第81行出现了new问题,正好找对了!
这里写图片描述

如果我把赋值语句加上,去掉赋值语句的注释,那么提示我在第90行出现malloc问题,第79和82行出现了new问题, (*゜ロ゜)ノ ,这个这个,找多了啊!!我又试了一次,这次按了log之后让它多跑了一段时间,还是这个样子。╮(╯▽╰)╭,这就是main里边跑的问题了,虽然它能更加准确地告诉我们问题发生在哪里,不过经常会误报,不过也不算大问题,误报的地方很容易就能被我们给发现啦。

LeakDiag的使用注意

这里也有一个严格的顺序:
1.启动vs程序
2.启动LeakDiag程序,在里边找到vs启动的这个程序
3.选择Heap Allocator,此时start亮了,点击它
4.过会点击Log按钮
5.稍等片刻点击stop按钮,关掉LeakDiag
6.结束vs程序

如果在点stop之前结束了vs程序,那么stop会按不下去。
如果首先启动LeakDiag,那么会找不到vs启动的程序
注意LeakDiag是按照进程PID来定位程序的,我每次启动vs得到的程序它的PID是不同的,所以必须要在vs启动之后再启动LeakDiag才能找到正确的vs程序。

我遇到过的内存泄漏情况

首先是类里边的:
1.类的动态对象
我简单地定义了如下的类

class MyClass
{
private:
    int a;
    int b;
    map<int, string> myMap;
public:
    //一开始只是写出MyClass();没有加{},然后提示我未定义的符号MyClass,确实是没定义哦
    MyClass()
    {   
        myMap[0] = "abc";
        myMap[2] = "def";
    }

    ~MyClass(){};
    void fun()
    {
        map<int, string>::iterator it;
        it = myMap.find(0);
        if (it != myMap.end())
        {
            cout << it->second.c_str() << endl;
        }
    }

};

之后我测试了如下代码:

        MyClass *myclass1 = new MyClass();
        myclass1->fun();
        //myclass->~MyClass();
        delete myclass1;//和其它的是一样的
        myclass1 = NULL;

提示我因为function =”operator new”导致内存泄漏。
●▂●什么鬼啊,我明明写了delete的啊,而且delete也会自动调用myclass类的析构函数,可为什么还是会报错呢?这个我真是没想到原因也不知道该怎么解决了,好奇怪啊。不知道是不是误报。

2.类里边的stl-map
依然是上边那个类的测试,提示因为map产生了内容泄漏。

function ="operator new"
function ="std___Tree_val..."
function ="std__map..."

如果直接在函数里边使用map是不会出现这个问题的,但是在类里边使用它就出现了这个问题。
这个问题我找到的解决办法就是在类的析构函数里边加上一句话:

myMap.clear();

如果是在类里边使用vector,那是没有问题的,这应该是因为vector和map申请新空间的方式不同引起的。

类里边的问题暂时就这么多了,下面记录下在opencv里边的问题。

1.IplImage的create
IplImage创建图像如果不主动释放是肯定会内存泄漏的,不过我挺好奇的是它load图像不知道会不会也有问题,所以对以下代码进行了测试。

IplImage* src = cvLoadImage("E:/1.jpg");

IplImage* temp = cvCreateImage( cvSize( 10, 10 ), src->depth, src->nChannels );  

IplImage* temp1 = cvCreateImage( cvSize( 10, 10 ), src->depth, src->nChannels );  
cvReleaseImage(&temp1);

第一个载入图片没问题,记录中关于load图像有下面这一段记录,从中可以看到这个cvLoadImage函数的调用顺序,最后还是依靠了malloc,不过这一段并没有dll=”memoryTest.exe”,说明在我们的程序里边它是不引起内存泄漏的。
这里写图片描述

第二句引起了问题,问题描述如下
这里写图片描述

解决办法便是第三句里边的加上cvReleaseImage。
一般来说使用了IplImage *的图像都要配套使用一个cvReleaseImage,所以我这里的载入图片也应该加上的。

2.Mat参数传递
我对Mat的载入图片,创建图片以及引用参数创建图片进行测试。

void matInFun(Mat &result){

    result = Mat(100, 100,CV_8U,cv::Scalar(0));   

}
void matInFun2(Mat result){

    result = Mat(100, 100,CV_8U,cv::Scalar(0));     
}

//测试Mat的创建,作为参数传递时,引用于非引用有区别
Mat matA, matB, matC;
matInFun(matA);
matInFun2(matB);

matInFun(matC);
matC.release();
////测试Mat的读取图片
Mat srcMat = imread("E:/1.jpg");

上面的代码在matInFun(matA)这一句上报错了,具体错误如下:
这里写图片描述
Mat的载入图片以及普通情况下的创建图片都是没有问题的,不过作为引用参数在函数里边创建图片的时候则会出现问题。
解决办法:使用完用引用创建的Mat之后,调用release()函数即可。

3.HoG特征的使用
HoG特征需要用到HOGDescriptor类,但是这个类申请之后该如何释放呢?

void testImage0()
{
    HOGDescriptor *hog=new HOGDescriptor(cvSize(128,32),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);  

}
void testImage1()
{
    Mat test = imread("E:/1.jpg");
    resize(test,test,Size(128,32),0,0,CV_INTER_LINEAR);
    HOGDescriptor *hog=new HOGDescriptor(cvSize(128,32),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);  
    vector<float>descriptors;//结果数组         
    hog->compute(test, descriptors,Size(1,1), Size(0,0)); //调用计算函数开始计算         
    cout<<"HOG dims: "<<descriptors.size()<<endl; 
    hog->~HOGDescriptor();//有这个就不会提示内存错误了

    //delete之后记得要赋值为NULL,避免野指针的情况出现
    /*delete hog;
    hog = NULL;*/
}
void testImageBad()
{
    Mat test = imread("E:/1.jpg");
    resize(test,test,Size(128,32),0,0,CV_INTER_LINEAR);
    HOGDescriptor *hog=new HOGDescriptor(cvSize(128,32),cvSize(16,16),cvSize(8,8),cvSize(8,8),9);  
    vector<float>descriptors;//结果数组         
    hog->compute(test, descriptors,Size(1,1), Size(0,0)); //调用计算函数开始计算         
    cout<<"HOG dims: "<<descriptors.size()<<endl; 

    delete hog;
    hog = NULL;
}

测试结果挺匪夷所思,
如果我是这样测试的

int main()
{
    testImage1();
    return 0;
}

或者

int main()
{
    testImageBad();
    return 0;
}

那么都不会报错。
但是如果我是这样写

这里写图片描述
下面报的错是在83行,也就是testImageBad函数,但是紧接着上一行的解释居然是function =”testImage1”,(#‵′)靠,什么奇怪的结果啊。
这里写图片描述

所以我觉得最好的解决办法就是把所有的都写一下:
先调用析构,然后delete,最好赋值为NULL,这样就肯定不会出错了。

展开阅读全文

没有更多推荐了,返回首页