内存泄漏时程序开发中经常遇到的问题. 而且出现内存泄漏很难检测,
但是其导致的结果却是灾难性的. 这里讲一下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中的内存泄漏检测