山寨STL实现之内存池V2

上一篇中我们已经实现了一个简单的内存池,可以申请更大块的内存块来减少申请小块内存块时产生的内存碎片。

在本篇中,我们需要为其加入内存泄漏的检测代码,以此来检测代码编写过程中的疏忽带来的内存泄漏。(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
10  #endif
11 
12  #endif
13         obj*      next;
14     };
15 
16      struct block
17     {
18         block*    next;
19          void*     data;
20 #ifdef _DEBUG
21         size_type size;
22  #if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
23         UINT_PTR  callStack[CALLSTACK_MAX_DEPTH];
24         DWORD     dwCallStackDepth;
25  #endif
26  #endif
27     };

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

然后,我们增加一个结构

1 #ifdef _DEBUG
2      struct use
3     {
4         obj* data;
5         use* next;
6     };
7  #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);
10     }
11     block* pBlock = (block*)malloc( sizeof(block));
12      while(pBlock == 0)
13     {
14         h( sizeof(block));
15         pBlock = (block*)malloc( sizeof(block));
16     }
17     pBlock->data = p;
18     pBlock->next = free_list;
19     free_list = pBlock;
20 
21     obj* current = (obj*)p;
22 #ifdef _DEBUG
23     addUseInfo(current);
24     current->released =  false;
25  #endif
26     current = (obj*)(( char*)current + preSize);
27      for( int j = 0; j < count - 1; ++j)
28     {
29 #ifdef _DEBUG
30         addUseInfo(current);
31         current->released =  true;
32  #endif
33         current->next = chunk_list[i];
34         chunk_list[i] = current;
35         current = (obj*)(( char*)current + preSize);
36     }
37      return ( char*)p + headerSize;
38 }

其中的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));
10 #ifdef _DEBUG
11     p = ( char*)p - ( int)headerSize;
12     obj* ptr = reinterpret_cast<obj*>(p);
13      if (ptr->released)  throw error< char*>("chunk has already released", __FILE__, __LINE__);
14     ptr->released =  true;
15  #endif
16     reinterpret_cast<obj*>(p)->next = chunk_list[i];
17     chunk_list[i] = reinterpret_cast<obj*>(p);
18 }


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;
10             Console::SetColor( truefalsefalsetrue);
11              throw error< char*>("chunk leaked", __FILE__, __LINE__);
12         }
13         free(ptr);
14         use_list = next;
15     }
16  #endif
17     clear();
18 }

其实说来也容易,只需要检测每个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/中找到。

你可能感兴趣的:(STL)