您的位置:首页 > 其它

山寨STL实现之内存池V2

2013-01-19 20:16 225 查看
上一篇中我们已经实现了一个简单的内存池,可以申请更大块的内存块来减少申请小块内存块时产生的内存碎片。

在本篇中,我们需要为其加入内存泄漏的检测代码,以此来检测代码编写过程中的疏忽带来的内存泄漏。(callstack的显示暂时仅支持Windows)

一、内存泄漏检测

首先,改写obj和block结构,在obj中加入一个域released表示这个chunk是否被释放

1 struct obj

2 {

3 #ifdef _DEBUG

4 bool released;

5

6 #if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) // Only windows can get callstack

7 #define CALLSTACK_MAX_DEPTH 30

8 UINT_PTR callStack[CALLSTACK_MAX_DEPTH];

9 DWORD dwCallStackDepth; // Real depth

#endif

#endif

obj* next;

};

struct block

{

block* next;

void* data;

#ifdef _DEBUG

size_type size;

#if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)

UINT_PTR callStack[CALLSTACK_MAX_DEPTH];

DWORD dwCallStackDepth;

#endif

#endif

};

其中的callstack部分将在下一节中介绍

然后,我们增加一个结构

#ifdef _DEBUG

struct use

{

obj* data;

use* next;

};

#endif

其中data域指向了一块分配出去的小内存块,next域形成了一张链表。

然后,我们添加一个成员变量来保存这张链表,以及一个函数来将一个chunk插入这张链表

#ifdef _DEBUG

use* use_list;

#endif

#ifdef _DEBUG

inline void MemoryPool::addUseInfo(obj* ptr)

{

use* p = (use*)malloc(sizeof(use));

p->data = ptr;

p->next = use_list;

use_list = p;

}

#endif

然后,我们来改写refill函数使其在分配内存块时打上released标记,并将每个分配的内存块记录下来

1 void* MemoryPool::refill(int i, void(*h)(size_type))

2 {

3 const int count = 20;

4 const int preSize = (i + 1) * ALIGN + headerSize;

5 char* p = (char*)malloc(preSize * count);

6 while(p == 0)

7 {

8 h(preSize * count);

9 p = (char*)malloc(preSize * count);

}

block* pBlock = (block*)malloc(sizeof(block));

while(pBlock == 0)

{

h(sizeof(block));

pBlock = (block*)malloc(sizeof(block));

}

pBlock->data = p;

pBlock->next = free_list;

free_list = pBlock;

obj* current = (obj*)p;

#ifdef _DEBUG

addUseInfo(current);

current->released = false;

#endif

current = (obj*)((char*)current + preSize);

for(int j = 0; j < count - 1; ++j)

{

#ifdef _DEBUG

addUseInfo(current);

current->released = true;

#endif

current->next = chunk_list[i];

chunk_list[i] = current;

current = (obj*)((char*)current + preSize);

}

return (char*)p + headerSize;

}

其中的headerSize跟callstack有关,将在下一节中介绍。

当然,在deallocate时要将此内存块的released标记打为true

1 void MemoryPool::deallocate(void* p, size_type n)

2 {

3 if(p == 0) return;

4 if(n > MAX_BYTES)

5 {

6 free(p);

7 return;

8 }

9 const int i = INDEX(ROUND_UP(n));

#ifdef _DEBUG

p = (char*)p - (int)headerSize;

obj* ptr = reinterpret_cast<obj*>(p);

if (ptr->released) throw error<char*>("chunk has already released", __FILE__, __LINE__);

ptr->released = true;

#endif

reinterpret_cast<obj*>(p)->next = chunk_list[i];

chunk_list[i] = reinterpret_cast<obj*>(p);

}

OK,现在已经有模有样了,可以松口气了。接下来是最重要的部分,在MemoryPool析构时检测这个Pool内的use_list中是否有chunk的released标记为true(内存泄漏了)

1 MemoryPool::~MemoryPool()

2 {

3 #ifdef _DEBUG

4 while (use_list)

5 {

6 use *ptr = use_list, *next = use_list->next;

7 if (!ptr->data->released)

8 {

9 obj* pObj = ptr->data;

Console::SetColor(true, false, false, true);

throw error<char*>("chunk leaked", __FILE__, __LINE__);

}

free(ptr);

use_list = next;

}

#endif

clear();

}

其实说来也容易,只需要检测每个chunk的released标记是否为true就行了,而最后的clear函数是以前析构函数的代码,用来释放所有申请的block和大块的chunk。

OK,现在我们已经可以检测出没有被deallocate的chunk了。

二、callstack

首先,我们先来看一个Windows API,“CaptureStackBackTrace”这个API通过传入的一个数组来得到一组地址。当然有这个API并不够,我们还需要知道是哪个文件的第几行。“SymGetSymFromAddr64”这个API用来获取某个地址对应的函数名,“SymGetLineFromAddr64”这个API则是用来获取某个地址对应的文件名和行号的,这两个函数的32位版本则是不带64的。有了这些Windows API,我们就可以很轻松的获取到当前函数的调用堆栈了,主要的功劳还是要归功于Windows强大的dbghelp。

最后,完整的代码你可以在http://code.google.com/p/qlanguage/中找到。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: