使用内存池管理对象内存

在需要频繁地 new/delete 对象时,很容易造成对象分配慢、内存碎片的产生。为提升应用程序在分配对象内存的效率,可以使用内存池管理对象内存的分配和释放。
作者按以下方式实现:

  • 编写模板类 CMemoryPool, 为每个类创建一个静态的内存池对象
  • 内存池类按大块向系统申请内存,将内存以静态双向链表进行关联,形成空闲链表
  • 分配类对象内存时,从空闲链表淘汰一个元素,并将此结点添加到已分配的双向链表中
  • 在内存池中将分配过的内存结点使用双向链表关联,同时实现应用程序退出后内存泄漏自动检查
  • 释放类对象内存时,从已分配的双向链表中移除结点元素,并归还给空闲链表中
  • 提供宏为类添加内存池的静态成员对象,并重载类的 operator new 和 operator delete 运算符
  • 不允许构造对象数组(此内存池内不支持动态创建对象数组)
  • 在使用时,只需要在类定义中加入 DECLARE_MEMORY_POOL 宏声明,在实现文件中加入 IMPLEMENT_MEMORY_POOL 宏实现
MemoryPool.h 文件中实现了模板类 CMemoryPool, 代码如下
#ifndef ___MEMORY__POOL__20141227___
#define ___MEMORY__POOL__20141227___
 
#include "OSType.h"
#include "Mutex.h"
#include <list>
#include <string>
#include <stdio.h>
#include <stdlib.h>
 
#define DECLARE_MEMORY_POOL(className) \
    private: \
        static CMemoryPool<className> m_memoryPool ## className; \
    public: \
        void* operator new(size_t size) \
        { \
            return m_memoryPool ## className.Allocate(); \
        } \
        void operator delete(void *pBuffer) \
        { \
            m_memoryPool ## className.Free(pBuffer); \
        } \
        void* operator new[](size_t size); \
        void operator delete[](void *pBuffer); \
        private:
 
#define IMPLEMENT_MEMORY_POOL(className) \
    CMemoryPool<className> className::m_memoryPool ## className(#className);
 
template <typename T>
struct SMemoryPoolNode
{
    T                       obj;
    SMemoryPoolNode*        pPrev;
    SMemoryPoolNode*        pNext;
};
 
// 定义内存池块的大小
#define MEMORY_POOL_BLOCK_SIZE 1048576LL
 
template <typename T>
class CMemoryPool
{
public:
    CMemoryPool(LPCSTR lpszClassName);
    ~CMemoryPool();
 
public:
    void* Allocate();
    void Free(void *pBuffer);
 
private:
    Bool NeedMoreBlock();
 
private:
    std::string                     m_strClassName;
    size_t                          m_nBlockSize;
    std::list<void *>                m_lstBlockList;
 
    SMemoryPoolNode<T>*               m_pFreeList;
    SMemoryPoolNode<T>*               m_pUsedList;
    CMutex                          m_lock;
};
 
template <typename T>
CMemoryPool<T>::CMemoryPool(LPCSTR lpszClassName) : m_strClassName(lpszClassName)
{
    m_nBlockSize = MEMORY_POOL_BLOCK_SIZE;
    m_nBlockSize = m_nBlockSize / sizeof(SMemoryPoolNode<T>) * sizeof(SMemoryPoolNode<T>);
    if (sizeof(SMemoryPoolNode<T>) > m_nBlockSize)
    {
        m_nBlockSize = 10 * sizeof(SMemoryPoolNode<T>);
    }
 
    m_pFreeList = NULL;
    m_pUsedList = NULL;
 
    NeedMoreBlock();
}
 
template <typename T>
CMemoryPool<T>::~CMemoryPool()
{
    // 打印出未释放的内存
    if (m_pUsedList != NULL)
    {
        SMemoryPoolNode<T> *ptr = m_pUsedList;
        while (ptr)
        {
            fprintf(stderr, "class %s: Memory leak found at %p with %lu B\n", m_strClassName.c_str(), ptr, sizeof(T));
            ptr = ptr->pNext;
        }
    }
 
    for (std::list<void *>::const_iterator it = m_lstBlockList.begin(); it != m_lstBlockList.end(); ++it)
    {
        free(*it);
    }
}
 
template <typename T>
void* CMemoryPool<T>::Allocate()
{
    CMutexLocker lock(&m_lock);
 
    if (NULL == m_pFreeList)
    {
        if (!NeedMoreBlock())
        {
            return NULL;
        }
    }
 
    SMemoryPoolNode<T> *pNode = m_pFreeList;
    m_pFreeList = m_pFreeList->pNext;
 
    if (m_pFreeList != NULL)
    {
        m_pFreeList->pPrev = NULL;
    }
 
    SMemoryPoolNode<T> *pLastUsedHead = m_pUsedList;
    m_pUsedList = pNode;
    m_pUsedList->pNext = pLastUsedHead;
    if (pLastUsedHead != NULL)
    {
        pLastUsedHead->pPrev = m_pUsedList;
    }
 
    return &pNode->obj;
}
 
template <typename T>
void CMemoryPool<T>::Free(void *pBuffer)
{
    CMutexLocker lock(&m_lock);
 
    SMemoryPoolNode<T> *pNode = (SMemoryPoolNode<T> *)pBuffer;
    if (pNode->pPrev != NULL)
    {
        pNode->pPrev->pNext = pNode->pNext;
         
        if (pNode->pNext != NULL)
        {
            pNode->pNext->pPrev = pNode->pPrev;
        }
    }
    else
    {
        m_pUsedList = pNode->pNext;
        if (m_pUsedList != NULL)
        {
            m_pUsedList->pPrev = NULL;
        }
    }
 
    pNode->pPrev = NULL;
    SMemoryPoolNode<T> *pLastFreeHead = m_pFreeList;
    m_pFreeList = pNode;
    m_pFreeList->pNext = pLastFreeHead;
    if (pLastFreeHead != NULL)
    {
        pLastFreeHead->pPrev = m_pFreeList;
    }
}
 
template <typename T>
Bool CMemoryPool<T>::NeedMoreBlock()
{
    void *ptr = malloc(m_nBlockSize);
    if (NULL == ptr)
    {
        return False;
    }
 
    Int32 nNodeCount = m_nBlockSize / sizeof(SMemoryPoolNode<T>);
    SMemoryPoolNode<T> *pLastNode = (SMemoryPoolNode<T> *)ptr;
 
    m_pFreeList = pLastNode;
    pLastNode->pPrev = NULL;
    pLastNode->pNext = NULL;
 
    for (Int32 i = 1; i < nNodeCount; ++i)
    {
        SMemoryPoolNode<T> *pNext = (SMemoryPoolNode<T> *)ptr + i;
        pNext->pPrev = pLastNode;
        pNext->pNext = NULL;
        pLastNode->pNext = pNext;
        pLastNode = pNext;
    }
 
    m_lstBlockList.push_back(ptr);
    return True;
}
 
#endif

以上包含了 OSTypes.h, 其包含了类型自定义,同时为了在多线程中使用,使用了互斥锁,并进行了封装。这二处的代码略之。

测试代码
#include <iostream>
#include <stdio.h>
#include "MemoryPool.h"
using namespace std;
 
class CMemoryTest
{
    DECLARE_MEMORY_POOL(CMemoryTest)
 
public:
    CMemoryTest()
    {
    }
 
    ~CMemoryTest()
    {
    }
 
private:
    char m_szBuff[100];
};
 
IMPLEMENT_MEMORY_POOL(CMemoryTest)
 
Int32 main()
{
    CMemoryTest *pTest1 = new CMemoryTest();
    CMemoryTest *pTest2 = new CMemoryTest();
 
    delete pTest1;
    delete pTest2;
 
    pTest1 = new CMemoryTest();
    pTest2 = new CMemoryTest();
     
//  delete pTest1;
//  delete pTest2;
 
    return 0;
}

测试结果如下:

class CMemoryTest: Memory leak found at 0x7f5d62518010 with 100 B
class CMemoryTest: Memory leak found at 0x7f5d62617f88 with 100 B

说明:

  • 在定义 DECLARE_MEMORY_POOL 宏时,使用 ## 将类名拼接到 m_memoryPool 的后面形成一个对象名称;
  • 在定义 IMPLEMENT_MEMORY_POOL 宏时,为了给 CMemoryPool 的构造函数传递类名字符串的参数,使用 # 将宏参数转换为字符串,在应用程序退出时,使用了内存池的类的 m_memoryPool ## className 对象会被销毁,在其析构函数中,会将未释放的内存打印出来,包括类名、内存地址及泄漏的字节数,非常便于调试内存泄漏。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,065评论 1 32
  • 内存是计算机非常关键的部件之一,是暂时存储程序以及数据的空间,CPU只有有限的寄存器可以用于 存储计算数据,而大部...
    dreamer_lk阅读 1,154评论 2 10
  • Java8张图 11、字符串不变性 12、equals()方法、hashCode()方法的区别 13、...
    Miley_MOJIE阅读 3,681评论 0 11
  • 内存管理 简述OC中内存管理机制。与retain配对使用的方法是dealloc还是release,为什么?需要与a...
    丶逐渐阅读 1,928评论 1 16
  • 嵌入式系统的内存管理 姓名:张猛 引用自:http://blog.csdn.net/baskmmu/article...
    oliverabc阅读 2,024评论 0 0