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的運行結果
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章