aihot  2017-05-29 18:50:43  OpenCV |   查看评论   

 内存泄漏时程序开发中经常遇到的问题. 而且出现内存泄漏很难检测,

但是其导致的结果却是灾难性的. 这里讲一下opencv中内存泄漏检测
的一些技巧.
 
OpenCV中关于内存管理主要涉及到以下3个函数:
  • CV_IMPL void  cvSetMemoryManager( CvAllocFunc alloc_func, CvFreeFunc free_func, void* userdata );
  • CV_IMPL void* cvAlloc( size_t size );
  • CV_IMPL void  cvFree_( void* ptr );
 
还有一个对应cvFree_的宏:
  • #define cvFree(ptr) (cvFree_(*(ptr)), *(ptr)=0)
 
宏cvFree的用处是在释放ptr指针对应的内存后, 将ptr设置为NULL.
 
这里我们先做个假设: opencv中所有的内存分配和释放都是通过cvAlloc和cvFree合作完成的.
如果你使用cvAlloc分配一个内存, 然后用delete来是释放内存是错误的(切记)!!!
 
因此, 如果我们能够跟踪到cvAlloc/cvFree的调用流程, 就可以分析内存泄漏的情况了.
 
一般情况下, 一个cvAlloc分配的内存最终必然要对应cvFree来释放, 如果cvAlloc/cvFree不是
匹配出现, 那么可以认为出现了内存泄漏.
 
为此, 我们需要定义自己的内存管理函数, 然后通过cvSetMemoryManager装载到opencv中.
内存管理函数的类型如下:
  • typedef void* (CV_CDECL *CvAllocFunc)(size_t size, void* userdata);
  • typedef int (CV_CDECL *CvFreeFunc)(void* pptr, void* userdata);
 
其中的userdata是用户通过cvSetMemoryManager来设置的. 我们可以简单的吧userdata当作一个
容器指针, 在每次执行我们自己的alloc_func/free_func函数时, 将内存的分配/释放情况记录到
userdata对应的容器.
 
为此, 我自己简单设计了一个MemTracker类:
  • #ifndef OPENCV_MEM_TRACKER_H
  • #define OPENCV_MEM_TRACKER_H
  •  
  • #include <stdio.h>
  • #include <vector>
  •  
  • // 内存泄漏追踪
  •  
  • class MemTracker
  • {
  • public:
  •    MemTracker(void);
  •    ~MemTracker(void);
  •  
  • private:
  •  
  •    // 登记分配/释放的内存
  •  
  •    void regAlloc(void *ptr, size_t size);
  •    void regFree(void *ptr);
  •  
  •    // 输出泄漏的内存
  •  
  •    int output(FILE* fp=stderr);
  •  
  • private:
  •    
  •    // 分配内存
  •  
  •    static void* alloc_func(size_t size, void *userdata);
  •  
  •    // 释放内存
  •  
  •    static int free_func(void *ptr, void *userdata);
  •  
  • private:
  •  
  •    struct Ptr
  •    {
  •       void *ptr;      // 内存地址
  •       size_t size;   // 内存大小
  •  
  •       Ptr(void *ptr, size_t size)
  •       {
  •          this->ptr = ptr;
  •          this->size = size;
  •       }
  •    };
  •  
  •    // 记录当前使用中的内存
  •  
  •    std::vector<Ptr>   m_memTracker;
  • };
  •  
  • #endif   // OPENCV_MEM_TRACKER_H
 
类的实现如下:
  • #include "MemTracker.h"
  •  
  • #include <assert.h>
  • #include <cv.h>
  •  
  • MemTracker::MemTracker(void)
  • {
  •    // 注册管理函数
  •  
  •    cvSetMemoryManager(alloc_func, free_func, (void*)this);
  • }
  •  
  • MemTracker::~MemTracker(void)
  • {
  •    // 取消管理函数
  •  
  •    cvSetMemoryManager(NULL, NULL, NULL);
  •  
  •    // 输出结果
  •  
  •    this->output();
  • }
  •  
  • // 登记分配/释放的内存
  •  
  • void MemTracker::regAlloc(void *ptr, size_t size)
  • {
  •    m_memTracker.push_back(Ptr(ptr, size));
  • }
  •  
  • void MemTracker::regFree(void *ptr)
  • {
  •    int i;
  •    for(i = 0; i < m_memTracker.size(); ++i)
  •    {
  •       // 删除记录
  •  
  •       if(m_memTracker[i].ptr == ptr)
  •       {
  •          m_memTracker[i] = m_memTracker[m_memTracker.size()-1];
  •          m_memTracker.pop_back();
  •          return;
  •       }
  •    }
  • }
  •  
  • // 输出泄漏的内存
  •  
  • int MemTracker::output(FILE* fp)
  • {
  •    int n = m_memTracker.size();
  •    int i;
  •  
  •    for(i = 0; i < n; ++i)
  •    {
  •       fprintf(fp, "%d: %p, %u/n", i, m_memTracker[i].ptr, m_memTracker[i].size);
  •    }
  •  
  •    return n;
  • }
  •  
  • // 分配内存
  •  
  • void* MemTracker::alloc_func(size_t size, void *userdata)
  • {
  •    assert(size > 0 && userdata != NULL);
  •  
  •    // 分配内存
  •  
  •    void *ptr = malloc(size);
  •    if(!ptr) return NULL;
  •  
  •    // 登记
  •  
  •    MemTracker *tracker = (MemTracker*)userdata;
  •    tracker->regAlloc(ptr, size);
  •  
  •    //
  •  
  •    return ptr;
  • }
  •  
  • // 释放内存
  •  
  • int MemTracker::free_func(void *ptr, void *userdata)
  • {
  •    assert(ptr != NULL && userdata != NULL);
  •  
  •    // 释放内存
  •  
  •    free(ptr);
  •  
  •    // 登记
  •  
  •    MemTracker *tracker = (MemTracker*)userdata;
  •    tracker->regFree(ptr);
  •  
  •    // CV_OK == 0
  •  
  •    return 0;
  • }
  •  
MemTracker在构造的时候会注册自己的内存管理函数, 在析构的时候会输出没有被释放的内存.
下面我们编写一个测试程序:
  • #include <cv.h>
  • #include <highgui.h>
  •  
  • #include "MemTracker.h"
  •  
  • int main()
  • {
  •    MemTracker mem;
  •  
  •    IplImage *img = cvLoadImage("lena.jpg", 1);
  •    if(!img) return -1;
  •  
  •    // 没有释放img内存
  •  
  •    // cvReleaseImage(&img);
  •  
  •    return 0;
  • }
 
 
在main函数退出的时候mem会被析构, 然后输出内存的泄漏情况. 下面是在我的电脑上测试的结果:
  • C:/work/vs2005/MemTracker/debug>MemTracker.exe
  • 0: 00C750C0, 112
  • 1: 00D90040, 786432
 
OK, 先说到这里吧, 下次再补充...
 
前面我们已经解决了内存泄漏的检测, 但是在出现内存泄漏的时候我们怎么才能
跟踪到出现内存泄漏的代码呢? 如果能够调试到没有被释放内存对应的cvAlloc函数就好了.
 
这个我们可以通过m_memTracker[i].ptr来比较内存的地址来检测, 例如在alloc_func中
添加以下代码, 然后设置断点:
  • // 检测00C750C0内存
  • if(ptr == (void*)00C750C0)
  • {
  •     // 设置断点
  • }
 
 
但是这个方法可能还有缺陷. 因为每次运行程序的时候, 内存的布局可能是有区别的.
最好的方法是把cvAlloc的调用顺序记录下来.
 
变动的部分代码:
  • class MemTracker
  • {
  •    struct Ptr
  •    {
  •       void *ptr;      // 内存地址
  •       size_t size;   // 内存大小
  •       int   id;
  •  
  •       Ptr(void *ptr, size_t size, int id)
  •       {
  •          this->ptr = ptr;
  •          this->size = size;
  •          this->id = id;
  •       }
  •    };
  •  
  •    // 记录当前使用中的内存
  •  
  •    std::vector<Ptr>   m_memTracker;
  •  
  •    // alloc_func对应的编号
  •  
  •    int               m_id;
  • };
  • MemTracker::MemTracker(void)
  • {
  •    m_id = 0;
  •  
  •    // 注册管理函数
  •  
  •    cvSetMemoryManager(alloc_func, free_func, (void*)this);
  • }
  • void MemTracker::regAlloc(void *ptr, size_t size)
  • {
  •    // 每次记录一个新的m_id
  •    m_memTracker.push_back(Ptr(ptr, size, m_id++));
  • }
  • // 输出泄漏的内存
  •  
  • int MemTracker::output(FILE* fp)
  • {
  •    int n = m_memTracker.size();
  •    int i;
  •  
  •    for(i = 0; i < n; ++i)
  •    {
  •       fprintf(fp, "%d: %p, %u/n", m_memTracker[i].id, m_memTracker[i].ptr, m_memTracker[i].size);
  •    }
  •  
  •    return n;
  • }
 
以后就可以根据m_memTracker[i].id来设置断点跟踪调试. 因为每次运行程序的时候, cvAlloc的调用次序是不变
的, 因此可以认为每次cvAlloc对应的id也是不变的. 这样就可以根据id来追踪出现内存泄漏的cvAlloc了.
 
对于"OpenCV扩展库", 可以将MemTracker直接集成到CvxApplication中, 这样就可以默认进行内存泄漏检测了.
内存检测先说到这里, 下一节我会简要分析一下OpenCV的cvAlloc等源代码 :D
 
前面的帖子中我们已经讨论了cvAlloc/cvFree_/cvSetMemoryManager等函数的使用技巧.
下面开始分析OpenCV中以上函数的实现代码. 我觉得如果在阅读代码之前, 如果能对函数的
用法有个基本的认识, 那么对于分析源代码是很有帮助的.
  • CV_IMPL  void*  cvAlloc( size_t size )
  • {
  •     void* ptr = 0;
  •     
  •     CV_FUNCNAME( "cvAlloc" );
  •  
  •     __BEGIN__;
  •  
  •     if( (size_t)size > CV_MAX_ALLOC_SIZE )
  •         CV_ERROR( CV_StsOutOfRange,
  •                   "Negative or too large argument of cvAlloc function" );
  •  
  •     ptr = p_cvAlloc( size, p_cvAllocUserData );
  •     if( !ptr )
  •         CV_ERROR( CV_StsNoMem, "Out of memory" );
  •  
  •     __END__;
  •  
  •     return ptr;
  • }
 
从代码我们可以直观的看出, cvAlloc分配的内存不得大于CV_MAX_ALLOC_SIZE, 即使是使用我们
自己的内存管理函数也会有这个限制.
 
然后通过p_cvAlloc对应的函数指针对应的函数来分配内存. p_cvAlloc是一个全局static变量, 对应的
还有p_cvFree和p_cvAllocUserData, 分别对应释放内存函数和用户数据. 它们的定义如下:
  • // 指向分配函数的指针,初始设置为默认值
  • static CvAllocFunc p_cvAlloc = icvDefaultAlloc;
  • static CvFreeFunc p_cvFree = icvDefaultFree;
  • static void* p_cvAllocUserData = 0;
 
 
 

除特别注明外,本站所有文章均为 赢咖4注册 原创,转载请注明出处来自OpenCV中的内存泄漏检测

留言与评论(共有 0 条评论)
   
验证码:
[lianlun]1[/lianlun]