简单实现定长内存池
Posted 两片空白
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单实现定长内存池相关的知识,希望对你有一定的参考价值。
目录
前言
我们知道用户申请内存必须通过操作系统,操作系统再从堆上开辟一段空间给用户。但是如果频繁的向操作系统申请空间,导致操作系统需要频繁的为用户开辟空间。但是操作系统需要管理很多东西,这样会导致操作系统的效率变得很低。
内存池是操作系统开辟的一大块内存,当用户需要申请空间时,直接去内存池申请空间,不需要通过操作系统。只有当内存池空间不够时,再向操作系统申请一大块空间,作为内存池。
这样用户需要空间时,就不需要频繁的向操作系统要空间。提高了效率。
一.定长内存池
1.1 概念
定长内存池:每次从内存池中分割内存的大小是定长的。
定长内存池是一大块内存空间,每次需要用户需要申请内存时,直接从内存池中分配即可。当用户空间使用完毕,不能直接释放,因为申请的时候申请的时一大块内存,不能释放小部分。定长内存池使用链表将小块内存连接起来,供下一次使用。
于是当用户向定长内存池申请空间时,定长内存池会先检测链表中是否有小块内存,有直接申请给用户,没有再从内存块中分出一部分给用户。
- 优点:简单粗暴,分配和释放的效率高,解决实际中特定场景下的问题有效。
不需要频繁向操作系统申请空间(这是内存池都拥有的优点),由于是定长的,不需要从链表中查找合适大小内存块,只需要直接分配即可。
- 缺点:功能单一,
只能解决定长的内存需求
,另外占着内存没有释放
。由于每次从内存池中分割内存的大小是定长的,如果定长内存池作为STL容器的内存池时,需要针对不同的容器,定义不同的定长内存池。因为不同容器的节点大小不同。vector还无法使用。
1.2 实现
1.2.1 成员变量介绍
成员变量包括一个链表头指针和一个大块内存。
struct Memory{
Memory(){
leftsize = 1024 * 1;
memory = (char *)malloc(leftsize);
std::cout << (void *)memory << std::endl;
head = memory;
}
char *memory = nullptr;//内存池,大块内存
Memory *next = nullptr;//指向下一个大块内存
size_t leftsize = 0;//剩余空间
char *head = nullptr;
~Memory(){
free(head);
}
};
//将大的内存块连接起来,方便释放
Memory *m = nullptr;
void *freelist = nullptr;//自由链表头指针,保存用户使用完的空间
为什么链表头节点直接定义成void*指针,而不是一个结构指针?
链表的结点不需要保存有效值,只需要保存下一个内存块的地址即可。我们直接利用切割的内存块空间来保存下一个内存块的地址。而void*可以接收任何指针类型。
比如:在32为平台下,利用分割内存块的前4字节来保存下一个内存块的地址。
为什么大块内存封装成一个结构体,而不是直接设置成一个指针?
为了方便释放空间,下面介绍。
结构体变量大块内存为什么char*类型?
char*步长为1字节,方便计算用户申请的空间。
1.2.2 成员函数介绍
定长内存池提供两个接口给用户,一个是向内存池申请空间,一个是将申请的空间归还给内存池。
向内存块申请空间
- 先检测自由链表是否有内存块
- 有内存块,直接头删内存块给用户。
- 如果没有,向大块内存切割内存块。如果,没有大块内存,需要向系统申请。如果有内存,直接切割内存给用户。
- 需要记录内存块的剩余空间大小,防止申请越界。如果剩余空间不够,需要向系统申请大块内存。
将申请的空间归还给内存池
- 直接将归还的空间头插到自由链表中
1.2.3细节
从大块内存中切个内存并不是真的直接切割,而是,将大块内存的头指针向后移动申请的大小位,之后不会将前面的空间分配给用户。
- 由于链表直接使用分割的内存块来保存下一个内存块的地址,如果申请的内存块小于指针字节数怎么处理?
做一个判断,当申请的内存块大小于当前平台的指针大小,直接向上取整,切割的内存块大小为指针大小。
//可能切割的内存块小于指针大小
static size_t GetSize(){
if (sizeof(T) < sizeof(void*)){
return sizeof(void*);
}
else{
return sizeof(T);
}
}
- 如何来让链表结点内存块来保存下一个内存块的地址?
利用强转和解引用
//32位平台下,强转成int*再解引用,得到4字节空间
*(int *)obj = freelist;
//64位平台下,强转成long long*,再解引用,得到8字节
*(long long*) obj = freelist;
- 32为平台下指针大小4字节,64位平台下指针大小8字节,链表内存块中如何分配空间来保存下一个内存块的地址?
//1.通过判断
if(sizeof(void*) == 4){
{
//32位平台下,强转成int*再解引用,得到4字节空间
*(int *)obj = freelist;
}
else{
//64位平台下,强转成long long*,再解引用,得到8字节
*(long long*) obj = freelist;
}
//2.直接得到指针大小空间,解引用时void*,直接是指针大小
*(void**)obj = freelist;
- 如何销毁定长内存池?
将大块内存池用链表连接起来,保存好大块内存的起始地址。销毁时,只需要直接遍历该链表,释放空间即可。这就是为什么要将申请的大块空间设置成结构体的原因。
struct Memory{
Memory(){
leftsize = 1024 * 1;
memory = (char *)malloc(leftsize);
std::cout << (void *)memory << std::endl;
head = memory;
}
char *memory = nullptr;//内存池,大块内存,char*步长一字节,方便分配
Memory *next = nullptr;//指向下一个内存池
size_t leftsize = 0;//剩余空间
char *head = nullptr;//保存内存块起始地址,方便释放
//析构函数
~Memory(){
free(head);
}
};
void Destroy(){
while (m){
Memory *tmp = m;
m = m->next;
delete tmp;//调用结构体析构函数,销毁空间
}
}
1.3 代码
#pragma once
#include "Comment.h"
struct Memory{
Memory(){
leftsize = 1024 * 1;
memory = (char *)malloc(leftsize);
std::cout << (void *)memory << std::endl;
head = memory;
}
char *memory = nullptr;//内存池,大块内存,char*步长一字节,方便分配
Memory *next = nullptr;//指向下一个内存池
size_t leftsize = 0;//剩余空间
char *head = nullptr;//保存内存块起始地址,方便释放
~Memory(){
free(head);
}
};
template<class T>
class ObjectPool{
private:
//将大的内存块连接起来,方便释放
Memory *m = nullptr;
void *freelist = nullptr;//自由链表头指针,保存用户使用完的空间
//可能切割的内存块小于指针大小
static size_t GetSize(){
if (sizeof(T) < sizeof(void*)){
return sizeof(void*);
}
else{
return sizeof(T);
}
}
//指针也可以不需要引用
static void*& GetIndex(T*& obj){
return (*(void **)obj);
}
void Destroy(){
while (m){
Memory *tmp = m;
m = m->next;
delete tmp;
}
}
public:
T* New(){
//首先去看链表中有没有
//没有去内存池中切
T* obj = nullptr;
if (freelist){
obj = (T*)freelist;
//链表的下一个位置
freelist = GetIndex(obj);
return obj;
}
else{
//可能没有内存池,需要去申请或者空间不够
if (m == nullptr || m->leftsize < sizeof(T)){
//连接起来
Memory *tmp = new Memory();
tmp->next = m;
m = tmp;
}
obj = (T*)m->memory;
m->memory += GetSize();
m->leftsize -= GetSize();
//定位new,再obj处开辟空间,并且可以调用构造函数
new(obj)T;
return obj;
}
}
void Delete(T* obj){
//调用析构
obj->~T();
//不能free,申请大块空间,不能free部分空间
//直接用切割的内存保存子下一个链表的地址
//强转成void**,再解引用,防止不同平台下指针字节数不同
//头插
GetIndex(obj) = freelist;
freelist = obj;
}
~ObjectPool(){
Destroy();
}
};
以上是关于简单实现定长内存池的主要内容,如果未能解决你的问题,请参考以下文章