日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

OpenCV中的内存泄漏检测

發(fā)布時間:2023/11/27 生活经验 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OpenCV中的内存泄漏检测 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

轉自:http://chaishushan.blog.163.com/blog/static/130192897200911685559809/

?

內存泄漏時程序開發(fā)中經常遇到的問題. 而且出現內存泄漏很難檢測,
但是其導致的結果卻是災難性的. 這里講一下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當作一個
容器指針, 在每次執(zhí)行我們自己的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)
{
? ? // 設置斷點
}


但是這個方法可能還有缺陷. 因為每次運行程序的時候, 內存的布局可能是有區(qū)別的.
最好的方法是把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中, 這樣就可以默認進行內存泄漏檢測了.
內存檢測先說到這里, 下一節(jié)我會簡要分析一下OpenCV的cvAlloc等源代碼?

前面的帖子中我們已經討論了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, 分別對應釋放內存函數和用戶數據. 它們的定義如下:

代碼:?全選
// pointers to allocation functions, initially set to default
static CvAllocFunc p_cvAlloc = icvDefaultAlloc;
static CvFreeFunc p_cvFree = icvDefaultFree;
static void* p_cvAllocUserData = 0;


默認的內存管理函數分別為icvDefaultAlloc和icvDefaultFree(icv開頭的表示為內部函數), 用戶數據指針為空.

繼續(xù)跟蹤默認的內存分配函數icvDefaultAlloc, 代碼如下:

代碼:?全選
static void*
icvDefaultAlloc( size_t size, void* )
{
? ? char *ptr, *ptr0 = (char*)malloc(
? ? ? ? (size_t)(size + CV_MALLOC_ALIGN*((size >= 4096) + 1) + sizeof(char*)));

? ? if( !ptr0 )
? ? ? ? return 0;

? ? // align the pointer
? ? ptr = (char*)cvAlignPtr(ptr0 + sizeof(char*) + 1, CV_MALLOC_ALIGN);
? ? *(char**)(ptr - sizeof(char*)) = ptr0;

? ? return ptr;
}


內部使用的是C語言中的malloc函數, 在分配的時候多申請了CV_MALLOC_ALIGN*((size >= 4096) + 1) + sizeof(char*)
大小的空間. 多申請空間的用處暫時先不分析.?

下面的cvAlignPtr函數用于將指針對其到CV_MALLOC_ALIGN邊界, 對于我們常規(guī)的PC來說是32bit, 也就是4字節(jié).
cvAlignPtr函數在后面會詳細討論.

下面語句將ptr0記錄到(ptr - sizeof(char*)), 可以把它看作一個指針. 最后返回ptr.
細心的朋友可能會發(fā)現, 前面malloc分配的是ptr0, 現在返回的卻是ptr, 這個是為什么呢?

這個的原因還是先放下(我也不懂), 但是返回ptr而不返回ptr0帶來的影響至少有2個:

1. 返回的ptr指針不能通過C語言的free函數釋放(這也是cvAlloc/cvFree必須配對使用的原因).
2. 在cvFree的時候, 可以根據(ptr - sizeof(char*))對應的值來檢測該內存是不是由icvDefaultAlloc申請.

這樣應該說可以增加程序的健壯性, icvDefaultFree可以不傻瓜似的對于任何指針都進行釋放.

下面來看看cvAlignPtr函數:

代碼:?全選
CV_INLINE void* cvAlignPtr( const void* ptr, int align=32 )
{
? ? assert( (align & (align-1)) == 0 );
? ? return (void*)( ((size_t)ptr + align - 1) & ~(size_t)(align-1) );
}


該函數的目的主要是將指針ptr調整到align的整數倍

其中align必須為2的冪, assert語言用于該檢測. 語句(align & (align-1))
一般用于將align的最低的為1的bit位設置為0. 如果為2的冪那么就只有1個為1
的bit位, 因此語句(x&(x-1) == 0)可以完成該檢測.

return語句簡化后為 (ptr+align-1)&~(align-1), 等價于((ptr+align-1)/align)*align.
就是找到不小于ptr, 且為align整數倍的最小整數, 這里對應為將指針對其到4字節(jié)(32bit).

cvFree_函數和cvAlloc類似, 就不詳細分析了:
代碼:?全選
CV_IMPL? void? cvFree_( void* ptr )
{
? ? CV_FUNCNAME( "cvFree_" );

? ? __BEGIN__;

? ? if( ptr )
? ? {
? ? ? ? CVStatus status = p_cvFree( ptr, p_cvAllocUserData );
? ? ? ? if( status < 0 )
? ? ? ? ? ? CV_ERROR( status, "Deallocation error" );
? ? }

? ? __END__;
}


p_cvFree默認值為icvDefaultFree:

代碼:?全選
static int
icvDefaultFree( void* ptr, void* )
{
? ? // Pointer must be aligned by CV_MALLOC_ALIGN
? ? if( ((size_t)ptr & (CV_MALLOC_ALIGN-1)) != 0 )
? ? ? ? return CV_BADARG_ERR;
? ? free( *((char**)ptr - 1) );

? ? return CV_OK;
}


最后我們簡要看下cvSetMemoryManager函數, 它主要用來設置用戶自己定義的內存管理函數:

代碼:?全選
CV_IMPL void cvSetMemoryManager( CvAllocFunc alloc_func, CvFreeFunc free_func, void* userdata )
{
? ? CV_FUNCNAME( "cvSetMemoryManager" );

? ? __BEGIN__;

? ? // 必須配套出現
? ??
? ? if( (alloc_func == 0) ^ (free_func == 0) )
? ? ? ? CV_ERROR( CV_StsNullPtr, "Either both pointers should be NULL or none of them");

? ? p_cvAlloc = alloc_func ? alloc_func : icvDefaultAlloc;
? ? p_cvFree = free_func ? free_func : icvDefaultFree;
? ? p_cvAllocUserData = userdata;

? ? __END__;
}


如果函數指針不為空, 則記錄到p_cvAlloc和p_cvFree指針, 如果為空則恢復到默認的內存管理函數.
需要注意的是if語句的條件(alloc_func == 0) ^ (free_func == 0), 只有當2個函數1個為NULL, 1個
不為NULL的時候才會出現, 出現這個的原因是內存管理函數的分配和釋放函數不匹配了, 這個是不允許的.

因此, 我們需要設置自己的內存管理函數, 就需要同時指定alloc_func和free_func函數, 清空的時候
則把2個參數都設置NULL就可以了.

今天再來補充一個小技巧??

我們前面通過cvSetMemoryManager函數來重新設置了自己的內存管理函數.
但是前面也說到過, 如果cvAlloc/cvFree覆蓋的周期和MemTracker相交, 那么
內存會出現錯誤.

即,

1. 原來OpenCV默認函數分配的內存可能使用我們自己的cvFree函數來釋放.
2. 我們自己定義的cvAlloc分配的內存可能使用原來OpenCV默認的函數來釋放.

這都會造成錯誤!

其實我們定義的目的只是要統計內存的使用情況, 我們并不想真的使用自己的函數的管理
OpenCV的內存. 道理很簡單, OpenCV的內存經過優(yōu)化, 對齊到某個字節(jié), 效率更好.

如果能獲取OpenCV原始的內存管理函數就好了, 但是沒有這樣的函數!!!

但是, 我們任然有方法來繞過這個缺陷.

我們可以在MemTracker::alloc_func函數進入之后, 在用cvSetMemoryManager恢復原來的
內存管理函數, 這樣我們統計目的也達到了, 而且還是用了OpenCV本身的函數來分配內存.

代碼如下:

代碼:?全選
void* MemTracker::alloc_func(size_t size, void *userdata)
{
? ?assert(size > 0 && userdata != NULL);
? ?
? ?// 取消管理函數

? ?cvSetMemoryManager(NULL, NULL, NULL);
? ?
? ?// 用OpenCV的方式分配內存

? ?void *ptr = cvAlloc(size);

? ?// 登記

? ?if(ptr)
? ?{
? ? ? MemTracker *tracker = (MemTracker*)userdata;
? ? ? tracker->regAlloc(ptr, size);
? ?}

? ?// 重新注冊注冊管理函數

? ?cvSetMemoryManager(alloc_func, free_func, userdata);

? ?return ptr;
}


MemTracker::free_func的方法和上面類似, 就不貼代碼了.

以后我們就可以透明的使用MemTracker了, 不管MemTracker對象在那個地方定義,
它對OpenCV的內存管理都不會有影響.

前面的方法雖然使得CvxMemTracker可以在任何地方使用, 但是可能帶來理解的難度.

因為 在cvAlloc之后進入的是 MemTracker::alloc_func, 但是在這個函數中又調用了cvAlloc!
這看起來很像一個無窮遞歸調用!!

但是實際的運行結果卻沒有出現無窮遞歸導致的棧溢出情形. 仔細分析就知道原理了:

1. 定義MemTracker對象

中間調用了 cvSetMemoryManager(alloc_func, free_func, (void*)this); 函數,
設置 MemTracker::alloc_func 為分配函數.

2. 調用cvAlloc

內部執(zhí)行到 MemTracker::alloc_func, 依次執(zhí)行

代碼:?全選
// 取消管理函數
? ?
? ?cvSetMemoryManager(NULL, NULL, NULL);


此刻, 分配函數又恢復為OpenCV的icvDefaultAlloc函數.
執(zhí)行

代碼:?全選
// 用OpenCV的方式分配內存
? ?
? ?void *ptr = cvAlloc(size);
? ?
? ?// 登記
? ?
? ?if(ptr)
? ?{
? ?? ?CvxMemTracker *tracker = (CvxMemTracker*)userdata;
? ?? ?tracker->regAlloc(ptr, size);
? ?}


這里的cvAlloc函數內部調用的是icvDefaultAlloc函數, 并不是MemTracker::alloc_func !!
就是這里了, alloc_func內部雖然調用了cvAlloc, 但是沒有執(zhí)行到alloc_func.

因此alloc_func不會出現遞歸.

最新的代碼可以參考下面:
http://opencv-extension-library.googlec ... mTracker.h
http://opencv-extension-library.googlec ... racker.cpp

總結

以上是生活随笔為你收集整理的OpenCV中的内存泄漏检测的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

歡迎分享!

轉載請說明來源于"生活随笔",并保留原作者的名字。

本文地址:OpenCV中的内存泄漏检测