zoukankan      html  css  js  c++  java
  • 山寨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/中找到。

  • 相关阅读:
    TPM Key相关概念
    (转)eclipse报错及解决说明 "XX cannot be resolved to a type "
    (转)Bat Command
    (转)Linux下查看文件和文件夹大小 删除日志
    (转)每天一个linux命令(50):crontab命令
    (转)Vi命令详解
    (转)maven打包时跳过测试
    (转)mybatis常用jdbcType数据类型
    (转)MyBatis在插入的数据有空值时,可能为空的字段都要设置jdbcType
    (转)mybatis:动态SQL
  • 原文地址:https://www.cnblogs.com/lwch/p/2868000.html
Copyright © 2011-2022 走看看