【玩具】内存池分配
一、引言
采用系统的内存分配策略,程序运行久了容易产生内存碎片,而且每次都从系统申请,然后释放归还给系统会消耗一定的性能。当然现在基本上都有现成的内存分配框架,例如tcmalloc。自己写除非是特殊应用场景,否则一般不会比系统默认的那套性能高效。本篇就介绍一个简单的内存池,属于玩具系列,不可在正式项目中使用。
二、简单内存池
根据上图,可以很清楚的了解这个内存池的设计方式,按照申请分配的内存大小进行分组,大小相同的被放在同一个链表里面。可用和已用链表分开,方便快速进行拿取和回收。具体的结构体如下:
/* * 内存分配的块 */ typedef struct _QMBlock { struct _QMBlock* pNext; //指向下一块 struct _QMBlock* pPrev; //指向上一块 void * pData; // 内存数据 }QMBlock; /* * 用于存储分配对应内存大小的块的列表 */ typedef struct _QMBlockList { QMBlock* pUsedBlock; //已使用的内存块(头) QMBlock* pLastUsedBlock; //已使用的内存块(尾) QMBlock* pFreeBlock; // 空闲的内存块(头) QMBlock* pLastFreeBlock; //空闲的内存块(尾) struct _QMBlockList* pNext; //指向下一个内存块列表 size_t mSize; // 当前内存块列表中块的大小 }QMBlockList;
具体实现就是申请内存时,先从QMBlockList链表中找有没有相同mSize大小的链表,没有则创建新的;找到QMBlockList后再看里面有没有可用的QMBlock内存块,如果没有则创建新的,找到则从可用链表移除。将可用QMBlock设置到已用链表中,然后设置校验信息并返回对应内存指针。
void* BindBlockData(QMBlockList* block_list, QMBlock* block) { #ifdef OPEN_BLOCK_HEAD_CHECK int* temp_ptr = (int*)block->pData; temp_ptr[0] = BLOCK_HEAD_TAG; //注意这里只能占用4个字节 intptr_t* ptr = (intptr_t*)(((char*)block->pData)+sizeof(int)); //强转为指针类型(跨平台) ptr[0] = (intptr_t)block_list; ptr[1] = (intptr_t)block; return &ptr[2]; #else intptr_t* ptr = (intptr_t*)block->pData; //强转为指针类型(跨平台) ptr[0] = (intptr_t)block_list; ptr[1] = (intptr_t)block; return &ptr[2]; #endif // OPEN_BLOCK_HEAD_CHECK }
外部使用的ptr指针地址其实前面有一段记录它属于哪个QMBlockList和QMBlock,并额外加入正确性校验。外部释放的时候则先拿到所属的对应QMBlockList和QMBlock,然后进行校验指针是否是对的,最后从已用链表移到可用链表。
int UnbindBlockData(void* p, QMBlockList** block_list, QMBlock** block { intptr_t* ptr = (intptr_t*)p; //强转 *block_list = (QMBlockList*)(*(ptr - 2)); //往前减 *block = (QMBlock*)(*(ptr - 1)); #ifdef OPEN_BLOCK_HEAD_CHECK char* temp_ptr = ((char*)p) - BLOCK_FLAG_SIZE; int check_k = *(int*)(temp_ptr); if (check_k != BLOCK_HEAD_TAG) { //校验失败了 return -1; } #endif // OPEN_BLOCK_HEAD_CHECK return 0; }
三、总结
实现内存池的好处不仅包括快速的分配和回收,还包括内存整理、防止重复释放、统计已分配的内存数量、定位分配的位置等等。但是一般写的内存分配99%可能性没有内置的好,且内存出bug难调试,大神除外。以上我写的就是个简单的玩具,源码放在GitHub上,欢迎大家批评教育。
欢迎微信搜索"游戏测试开发"关注一起沟通交流。