c++实现内存池并应用到STL容器

 

内存池:

     为了节省申请小块内存与释放小块内存时的开销,可以使用内存池,开始时根据需要申请一片较大的内存,在申请小块内存的时候从内存池中获取,在释放小块内存时,将内存放回内存池

内存池中的内存块:

    块的大小可以由内存池的编写者确定,一般为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):

图1 ALIGN为8时,main的运行结果

 

运行结果解析:

vector<int, myalloc<int> > v(a,a+5);  //申请20个字节的内存,32位程序int占4字节,因此有:

申请20字节,但是提升到8的倍数,所有申请到24个字节,但是对于vector,它会认为是20字节;9代表当前24字节区块链中还有9个24字节的块:

图2 执行vector<int, myalloc<int> > v(a,a+5)后

v.push_back(5);  //当前vector中含有5个int,占用24字节,而原本有20字节,因此申请原来两倍的空间20*2 = 40字节:

图3 执行v.push_back(5)后,申请新内存(40字节)

执行完v.push_back(5); 之后,申请了更大的空间,原来的空间被回收,可以看到回收的内容为(0,1,2,3,4,没有用到的内存)

当加入24字节区块链后,block*next指针指向了原来的头部0x1e0fe0(看上图),24字节区块链的长度也因此变成了10:

图4 执行v.push_back(5)后,释放原来的内存(对于vector为20字节,对于memory_pool为24字节)

执行v.clear(); 后,vector的内容被清除或者说vector的end指针的指向和begin相同,但是vector掌管的动态内存并没有被回收。

执行v.push_back(9);后,相当于v[0] = 9;

程序运行结束后,释放了vector所掌管的动态内存:

运行结果2:

图5 ALIGN为4时,main的运行结果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章