C++内存池(附源码)Word文档下载推荐.docx
- 文档编号:13012746
- 上传时间:2022-10-02
- 格式:DOCX
- 页数:16
- 大小:18.32KB
C++内存池(附源码)Word文档下载推荐.docx
《C++内存池(附源码)Word文档下载推荐.docx》由会员分享,可在线阅读,更多相关《C++内存池(附源码)Word文档下载推荐.docx(16页珍藏版)》请在冰豆网上搜索。
unionMemNode{
MemNode*_next;
char_data[1];
};
union每个成员的起始地址都是开头的位置,所以每次仅能使⽤⼀个成员,在链表中由_next指向下个内存块的地址,在分配内存时由_data指向内存⾸地址,长度为1的数组放在结构体最后⼀个成员位置,可以访问给结构体多分配的地址空间,这种技术叫做柔性数组。
这样做的好处减少了对内存块管理时额外的内存损耗。
想想我们学习数据结构时实现的链表,都是通过结构体的⼀个成员来指向下个节点的地址,多出了⼀个指针4Byte的内存消耗。
参考STL,我们内存块的管理如下图所⽰:
有同学要问了,那我要是申请⽐128更⼤的内存怎么办?
SGI这⾥就直接⾛正常的内存申请,还是会有系统调⽤产⽣。
因为系统对于程序请求的内存,管理时也会⽣成额外的内存控制数据占⽤内存,这样申请的内存越⼩,额外占⽤的内存⽐例就越⾼。
我们每次申请指定量的内存,然后将内存格式化到块管理的数组链表中。
char*res;
size_tneed_bytes=size*nums;
size_tleft_bytes=_pool_end-_pool_start;
//内存池够⽤
if(left_bytes>
=need_bytes){
res=_pool_start;
_pool_start+=need_bytes;
returnres;
}elseif(left_bytes>
=size){
nums=left_bytes/size;
need_bytes=size*nums;
}
size_tbytes_to_get=size*nums;
if(!
is_large){
0){
MemNode*my_free=_free_list[FreeListIndex(left_bytes)];
((MemNode*)_pool_start)->
_next=my_free;
_free_list[FreeListIndex(size)]=(MemNode*)_pool_start;
}else{
free(_pool_start);
_pool_start=(char*)malloc(bytes_to_get);
//内存分配失败
if(0==_pool_start){
throwstd:
:
exception("
Therememaryisnotenough!
"
);
_malloc_vec.push_back(_pool_start);
_pool_end=_pool_start+bytes_to_get;
returnChunkAlloc(size,nums,is_large);
将返回的内存添加到块管理队列中
my_free=&
(_free_list[FreeListIndex(size)]);
*my_free=next=(MemNode*)(chunk+size);
for(inti=1;
;
i++){
current=next;
next=(MemNode*)((char*)next+size);
if(nums-1==i){
current->
_next=nullptr;
break;
_next=next;
内存的分配和回收
每次从系统申请内存时都通过⼀个辅助函数将内存增到为8的倍数,上层请求内存时寻找最⼩能容纳当前请求的头节点索引
//获取size最⼩8的倍数
size_tRoundUp(size_tsize){
return((size+__align-1)&
~(__align-1));
//获取容纳当前size的最⼩内存块索引
size_tFreeListIndex(size_tsize){
return(size+__align-1)/__align-1;
当找到索引位置时,如果内存块不为空,则取出当前内存块,将之后的链表节点向前移动,如果内存不够的话,再次向系统请求新的内存。
std:
unique_lock
lock(_mutex);
MemNode**my_free=&
(_free_list[FreeListIndex(sz)]);
MemNode*result=*my_free;
if(result==nullptr){
void*bytes=ReFill(RoundUp(sz));
memset(bytes,0,sz);
returnbytes;
*my_free=result->
_next;
memset(result,0,sz);
returnresult;
内存回收时与此理相同,通过辅助函数找到索引位置,将内存块放⼊⾸部位置,之前的内存块后移。
MemNode*node=(MemNode*)m;
(_free_list[FreeListIndex(len)]);
node->
_next=*my_free;
*my_free=node;
m=nullptr;
⼤块内存的分配和回收
通过以上的内存管理,我们⾜以解决⼩块内存的⾮配和回收,但是还可能存在另⼀种需求,类似Nginx内存池有⼤块内存的管理,我们在实际开发中也会⽤到诸如接收发送缓存之类的需求。
这⾥添加增加⼀个新的类,以管理⼤块的内存块,⽽且要⽀持动态的增减:
通过⼀个vector来管理池中空闲的内存块,需要注意的是析构时要将所有从池中申请的内存还给内存池,不然就需要⾃⼰⼿动释放。
申请内存块的⽣命周期管理可以交给。
对象初始化
与C语⾔实现内存池的不同之处在于,C语⾔可以只负责内存的分配⽽不⽤管内部数据的初始化,因为C语⾔没有对象的概念。
但是在C++中,我们不仅仅要负责内存的分配,还要调⽤构造函数负责对象的初始化。
⼤家知道C++中的new操作符,⼀是负责内存申请,⼆是调⽤构造函数实现对象初始化。
⽽C++中可以通过可变模板参数来实现任意数量任意参数的函数转发,再辅之std:
forward完美转发,即可实现构造函数的调⽤功能。
所以我实现的内存池对外提供内存申请的接⼝有三个:
//forobject.invocationofconstructorsanddestructors
template
T*PoolNew(Args&
&
...args);
voidPoolDelete(T*&
c);
//forcontinuousmemory
T*PoolMalloc(intsize);
voidPoolFree(T*&
m,intlen);
//forbulkmemory.
//returnonebulkmemorynode
T*PoolLargeMalloc();
voidPoolLargeFree(T*&
m);
这样每次请求和释放都需要调⽤接⼝,C++对这种操作最熟悉不过,我们交给智能指针来管理即可。
还有这⾥为什么没有重载new操作符来呢?
因为new和delete的重载函数只能是static函数(因为new对象的时候,对象还没有创建),所以内存池的api通过重载new和delete实现,看起来很美好,但实际上是⾏不通的。
我们要创建内存池的对象,每个内存池的对象管理的都是不同的内存。
下⾯看下PoolNew调⽤构造函数的过程。
T*CMemaryPool:
PoolNew(Args&
...args){
intsz=sizeof(T);
if(sz>
__max_bytes){
void*bytes=malloc(sz);
T*res=new(bytes)T(std:
forward(args)...);
T*res=new(result)T(std:
到这⾥基本上所有的功能都已经实现完毕。
但是既然我们⽀持创建内存池的对象,那什么时候释放内存池占有的内存呢?
当然是析构函数中!
但是怎么释放呢?
我们是通过malloc库函数申请的内存,释放的时候⾃然是去调⽤free释放。
但是我们不能通过循环块的数组和链表去释放内存。
因为我们申请的时候是⼀整块去申请的,释放的时候只要通过每次申请的头地址去释放即可。
所以我在这⾥添加了⼀个辅助的std:
vector来存储每次申请内存的地址,释放的时候只遍历这个std:
vector即可。
//声明
vector
_malloc_vec;
//存储
//释放
for(autoiter=_malloc_vec.begin();
iter!
=
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C+ 内存 源码