内存池:
为了节省申请小块内存与释放小块内存时的开销,可以使用内存池,开始时根据需要申请一片较大的内存,在申请小块内存的时候从内存池中获取,在释放小块内存时,将内存放回内存池
内存池中的内存块:
块的大小可以由内存池的编写者确定,一般为8的倍数:8,16,24,32,.....,128;单位为字节,相同大小的块通过指针串成一串(和单链表一样)
内存池的两个部分:
1.未区块化的内存:这是一片连续的内存,可以通过两个指针 begin和end 表示它
2.区块化的内存:区块化的内存通过链表的形式保存着
在申请小块内存的时候,是从区块内存链中取,而不是从未区块化的内存中取
内存池中的区块化:
将大片的内存转换成许多内存块,如:将80个字节连续的内存,转换成10个大小为8字节的内存,同时需要将这10个内存块像链表一样串起来
为什么需要块?
因为如果没有块,在回收内存的时候,回收的内存可能与内存池中未区块化的内存是不连着的,所有没办法通过简单的两个指针来表示他们。
实现:
#include <cstddef>
#include <cstdlib>
#include <climits>
#include <iostream>
#define ALIGN 8 //最小的内存块及内存块增加的梯度
#define MAX_BYTES 128 //最大的内存块
template<int inst>
class memory_pool{
private:
union block{
union block* next; //指向下一个区块
char d[1];
};
static block * volatile free_list[MAX_BYTES/ALIGN]; //数组元素指向区块化的链表
static char *begin; //未区块化的内存池的头
static char *end; //未区块化的内存池的尾
static size_t heap_number; //内存池向堆申请内存的次数
private:
//将n上升到ALIGN的倍数
static size_t ALIGN_BYTES(size_t n){
return ( (n+ALIGN - 1)/ALIGN) * ALIGN;
}
//根据n的大小,获取free_list的下标
static size_t FREE_LIST_INDEX(size_t n){
return (n - 1)/ALIGN;
}
//从未区块化的内存中获取内存,或者向堆申请
static void *getMemory(size_t bytes, size_t& blocks){
size_t bytes_remain = end - begin; //现在还拥有的未区块化的内存,单位为字节
size_t bytes_need = bytes * blocks; //需要获取的内存,单位为字节
void *result;
//(1).可申请到所有需要的区块
if(bytes_remain >= bytes_need){
result = begin;
begin += bytes_need;
return result;
}
//无法满足(1),但是可以申请1个或以上的区块
if(bytes_remain >= bytes){
blocks = bytes_remain / bytes; //可以获取的块数
result = begin;
begin += blocks * bytes; //调整为区块化的内存的范围
return result;
}
if(bytes_remain > 0){
//连1个区块都无法申请,则将剩余的未区块化的内存区块化
block * volatile * free_list_now = free_list + FREE_LIST_INDEX(bytes_remain);
block* new_block = (block *)begin; //剩余的内存区块化
block* old_block = *free_list_now; //链表拼接
new_block->next = old_block; //链表拼接
*free_list_now = new_block; //链表拼接
begin += bytes_remain; //调整为区块化的内存的范围,该语句执行后表示已无未区块化内存
}
//从堆中申请内存
void * alloc_storage = 0;
size_t alloc_number = bytes_need * 2 + ALIGN_BYTES(heap_number << 1); //申请字节数,随着申请次数增加而增加
alloc_storage = malloc(alloc_number);
//申请alloc_number个字节的内存成功,调整begin以及end后,返回需要的内存:bytes_need个字节
if(alloc_storage != 0){
begin = (char*)alloc_storage;
end = begin + alloc_number;
result = begin;
begin += bytes_need;
return result;
}
//内存申请失败,调用oom_malloc,申请bytes个字节的内存
result = first_alloc<1>::oom_malloc(bytes);
if(result == 0){
blocks = 0;
}else{
blocks = 1;
}
return result;
}
//内存池第二级空间配置,请求将未区块化的内存区块化
static void *blocking(size_t n){
size_t blocks = 10; //请求10个区块
char * alloc_storage = (char*)getMemory(n, blocks); //申请空间,blocks传递的是引用类型
void * result = alloc_storage;
if(blocks == 1){
return result;
}else if(blocks == 0){
return 0;
}
//申请到大于1个区块,第一个用于返回给用户,之后的区块加入区块链
block* volatile * free_list_now = free_list + FREE_LIST_INDEX(n); //数组的头部
block* itr_storage = (block*)(alloc_storage + n); //每次以n的偏移遍历申请到的内存
block* block_line_head = *free_list_now; //指向区块大小为n的区块链的头部
//连接申请到的内存
for(int i = 1; i < blocks; ++i){
itr_storage->next = block_line_head;
block_line_head = itr_storage;
itr_storage = (block*)((char*)itr_storage + n); //这里注意,需要先将指针转换为char*,以让其偏移的单位为字节
}
*free_list_now = block_line_head;
return result;
}
public:
/*
//仅用于查看区块链,无其他作用
static void print(char x[100] = "defult", int a = -1){
std::cout << x << " " << a <<std::endl;
for(int i = 0; i < 16; ++i){
if(free_list[i] != 0){
std::cout << (i+1)*ALIGN << " : " << free_list[i] << " ";
block* j= free_list[i];
int k = 0;
while(j){
j = j->next;
k++;
}
std::cout << k << std::endl;
}
}
}
*/
//内存池第一级空间配置,从区块中获取内存
static void *allocate(size_t n){
//当大于最大内存块时,调用 malloc获取内存
if(n > MAX_BYTES)
return malloc(n);
block* volatile * free_list_now = free_list + FREE_LIST_INDEX(n); //获取相应的链表
block* p = *free_list_now;
void* result = 0;
if(p != 0){ //链表中有内存块
*free_list_now = p->next;
result = p;
}else{ //链表中无内存块,申请内存块
result = blocking(ALIGN_BYTES(n));
}
//---------查看各个区块链,可以删除
//print((char*)"申请内存/字节:",n);
return result;
}
static void deallocate(void *p, size_t n){
/*
//---------------检查释放的内存,看看释放了什么,可以删除
for(int i = 0; i < ALIGN_BYTES(n)/4; ++i){
std::cout << (int*)((int*)p)[i] << ",";
}
std::cout << "\n";
*/
//当释放的内存大于最大内存块时,通过free回收
if(n > MAX_BYTES)
free(p);
else{
block* volatile * free_list_now = free_list + FREE_LIST_INDEX(n);
block* link_head = *free_list_now;
block* temp = (block*)p;
temp->next = link_head;
*free_list_now = temp;
//print((char*)"释放内存/字节:", n);
}
/*
//---------------在将内存拼接到链表后再检查一次
for(int i = 0; i < ALIGN_BYTES(n)/4; ++i){
std::cout << (int*)((int*)p)[i] << ",";
}
std::cout << "\n";
*/
}
};
template<int inst> char* memory_pool<inst>::begin = 0;
template<int inst> char* memory_pool<inst>::end = 0;
template<int inst> size_t memory_pool<inst>::heap_number = 1;
template<int inst> typename memory_pool<inst>::block* volatile memory_pool<inst>::free_list[MAX_BYTES/ALIGN] = {0};
此处,不知道可不可以将template<int inst>中的inst变为ALIGN,从而更简单的创建不同的memory_pool,这取决于inst是否有什么实际作用
成员变量:
free_list | 一个数组,数组的元素为指针,指向每个区块链的头 |
begin | 一个指针,指向未区块化内存的头部,值仅在getMemory中改变 |
end | 一个指针,指向未区块化内存的尾部+1,值尽再getMemory中改变;采用半开半闭表示法[begin,end) |
heap_number | 一个整数,随着调用malloc(仅再未区块化内存不足的时候调用)的次数增加而增加 |
私有成员方法:
方法名 | 参数 | 返回类型 | 功能 |
ALIGN_BYTES | size_t n 表示要对齐的字节 | size_t 表示对齐后的字节 | 将字节对齐,以符合内存块的大小 |
FREE_LIST_INDEX | size_t n 表示字节 | size_t 表示free_list的下标 | 根据n获取free_list的下标 |
getMemory |
size_t bytes 内存块的大小 size_t& blocks 申请的块的数量 |
void * 指向获得的内存的首字节 | 申请一片连续的,大小为bytes*blocks的内存,返回指向内存的指针,注意blocks为引用,因此实际申请到的内存为该函数调用结束后的blocks*bytes |
blocking | size_t n 需要获取的内存块的大小 | void * 指向获取的内存块 | 当该函数被调用时,表示区块链中已无内存块,需要调用getMemory申请内存,同时将得到的内存区块化 |
公有成员方法:
方法名 | 参数 | 返回类型 | 功能 |
allocate | size_t n 需要申请的字节数 | void * 指向获得的内存 | 从区块链中,获取1个区块,区块的大小未ALIGN_BYTES(n) |
deallocate |
void *p 指向需要释放的内存 size_t n 需要释放的内存的大小 |
void | 释放内存,将需要释放的内存拼接回区块链 |
测试:
内存配置器可参考https://blog.csdn.net/A_littleK/article/details/102639016
//allocator.h
//内存配置器,可参考https://blog.csdn.net/A_littleK/article/details/102639016
template<class T, class Alloc = memory_pool<1> >
class myalloc{
public:
//以下的 typedef 是必须的,因为在容器中有类似 _alloc::pointer 的语法
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef const T& const_reference;
typedef T& reference;
typedef size_t size_type;
typedef ptrdiff_t difference_type;
template<class U>
struct rebind{
typedef myalloc<U> other;
};
pointer allocate(size_type size, const void* hint = 0){
return (pointer)Alloc::allocate(size * sizeof(T) );
};
pointer allocate(){
return (pointer)Alloc::allocate( sizeof(T) );
};
void deallocate(pointer p, size_type n){
Alloc::deallocate(p, n * sizeof(T));
}
void deallocate(pointer p){
Alloc::deallocate(p, sizeof(T));
}
void construct(pointer p, const_reference x){
new(p) T(x);
}
void destory(pointer p){
p->~T();
}
pointer address(reference x){
return (pointer)&x;
}
const_pointer const_address(const_reference x){
return (const_pointer)&x;
}
size_type max_size() const {
return size_type(UINT_MAX/sizeof(T));
}
};
main:
#include "allocator.h"
#include <memory>
#include <cstdlib>
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int main(){
int a[5] = {0,1,2,3,4};
vector<int, myalloc<int> > v(a,a+5);
v.push_back(5);
v.clear();
v.push_back(9);
return 0;
}
运行结果1(ALIGN为8):
运行结果解析:
vector<int, myalloc<int> > v(a,a+5); //申请20个字节的内存,32位程序int占4字节,因此有:
申请20字节,但是提升到8的倍数,所有申请到24个字节,但是对于vector,它会认为是20字节;9代表当前24字节区块链中还有9个24字节的块:
v.push_back(5); //当前vector中含有5个int,占用24字节,而原本有20字节,因此申请原来两倍的空间20*2 = 40字节:
执行完v.push_back(5); 之后,申请了更大的空间,原来的空间被回收,可以看到回收的内容为(0,1,2,3,4,没有用到的内存)
当加入24字节区块链后,block*next指针指向了原来的头部0x1e0fe0(看上图),24字节区块链的长度也因此变成了10:
执行v.clear(); 后,vector的内容被清除或者说vector的end指针的指向和begin相同,但是vector掌管的动态内存并没有被回收。
执行v.push_back(9);后,相当于v[0] = 9;
程序运行结束后,释放了vector所掌管的动态内存: