從零開始寫STL-內存部分-內存分配器allocator


從零開始寫STL-內存部分-內存分配器allocator

內存分配器是什么?

一般而言,c++的內存分配和釋放是這樣操作的

class Foo{ //...};
Foo* pf = new Foo;//配置內存,然后建構對象
delete pf; //將對象解構,然后釋放內存
  其中的 new操作內含兩階段動作:(1)調用::operator new配置內存,(2) 調用Foo::Foo()建構對象內容。delete操作也內含兩階段動作: (1)調用Foo::~Foo()將對象解構,(2)調用::operator delete釋放內存。
  為了精密分工,STL allocator決定將這兩階段區分開來。內存配置由alloc:allocate()負責,內存釋放由alloc::deallocate()負責; 對象建構由::construct()負責,對象析構由::destroy()負責。

題外話 對於new 和 delete

為了避免對后面析構函數 和 內存回收的部分產生一些基本疑問,對new 和 delete做一些總結

  • new 的調用過程
    new -> operator new -> malloc -> 構造函數
  • operator new 源碼解析
    avatar

construct 與 destory

//在分配好的內存上調用T1類的構造參數
//T2 應該是能被T1類型的構造參數接收的類型或者可以隱式轉換為可接受類型的值
template<class T1, class T2>
inline void construct(T1* p, const T2& value)
{
	new(p) T1(value);//調用placement new 
	// 在已經分配好的內存上調用構造函數,不能用delete釋放
}

template<class T>
inline void destroy(T* ptr)
{
	ptr->~T();//泛型析構
}

allocator 源碼分析

可以看到內存的分配是通過alloc函數來進行的,進行指針類型轉換之后調用對應的泛型構造和析構函數。

namespace ministl
{
	template<class T>
	class allocator {
	public:
		typedef T			value_type;
		typedef T*			pointer;
		typedef const T*	const_pointer;
		typedef T&			reference;
		typedef const T&	const_reference;
		typedef size_t		size_type;
		typedef ptrdiff_t	difference_type;
	public:
		static T *allocate();
		static T *allocate(size_t n);
		static void deallocate(T *ptr);
		static void deallocate(T *ptr, size_t n);

		static void construct(T *ptr);
		static void construct(T *ptr, const T& value);
		static void destroy(T *ptr);
		static void destroy(T *first, T *last);
	};

	template<class T>
	T *allocator<T>::allocate() {
		return static_cast<T *>(alloc::allocate(sizeof(T)));//指針類型轉換
	}
	template<class T>
	T *allocator<T>::allocate(size_t n) {
		if (n == 0) return 0;
		return static_cast<T *>(alloc::allocate(sizeof(T) * n));
	}
	template<class T>
	void allocator<T>::deallocate(T *ptr) {
		alloc::deallocate(static_cast<void *>(ptr), sizeof(T));
	}
	template<class T>
	void allocator<T>::deallocate(T *ptr, size_t n) {
		if (n == 0) return;
		alloc::deallocate(static_cast<void *>(ptr), sizeof(T)* n);
	}

	template<class T>
	void allocator<T>::construct(T *ptr) {
		new(ptr)T();
	}
	template<class T>
	void allocator<T>::construct(T *ptr, const T& value) {
		new(ptr)T(value);
	}
	template<class T>
	void allocator<T>::destroy(T *ptr) {
		ptr->~T();
	}
	template<class T>
	void allocator<T>::destroy(T *first, T *last) {
		for (; first != last; ++first) {
			first->~T();
		}
	}
}

真正的底層內存分配器 Alloc

Alloc的內存分配分為兩級,一級是大於128KB的內存塊管理,直接通過malloc 和 free來進行,小於128KB的內存管理,是通過維護一個內存池來實現的。

	class alloc {
	private:
		enum EAlign { ALIGN = 8 };//小型區塊的上調邊界
		enum EMaxBytes { MAXBYTES = 128 };//小型區塊的上限,超過的區塊由malloc分配
		enum ENFreeLists { NFREELISTS = (EMaxBytes::MAXBYTES / EAlign::ALIGN) };//free-lists的個數
		enum ENObjs { NOBJS = 20 };//每次增加的節點數
	private:
		//free-lists的節點構造
		//節省內存你,既可以用來存儲數據也可以用來存儲指向下一個節點的指針
		union obj {
			union obj *next;
			char client[1];
		};

		static obj *free_list[ENFreeLists::NFREELISTS];
	private:
		static char *start_free;//內存池起始位置
		static char *end_free;//內存池結束位置
		static size_t heap_size;//
	private:
		//將bytes上調至8的倍數
		static size_t ROUND_UP(size_t bytes) {
			return ((bytes + EAlign::ALIGN - 1) & ~(EAlign::ALIGN - 1));
		}
		//根據區塊大小,決定使用第n號free-list,n從0開始計算
		static size_t FREELIST_INDEX(size_t bytes) {
			return (((bytes)+EAlign::ALIGN - 1) / EAlign::ALIGN - 1);
		}
		//返回一個大小為n的對象,並可能加入大小為n的其他區塊到free-list
		static void *refill(size_t n);
		//配置一大塊空間,可容納nobjs個大小為size的區塊
		//如果配置nobjs個區塊有所不便,nobjs可能會降低
		static char *chunk_alloc(size_t size, size_t& nobjs);

	public:
		static void *allocate(size_t bytes);
		static void deallocate(void *ptr, size_t bytes);
		static void *reallocate(void *ptr, size_t old_sz, size_t new_sz);
	};

靜態初始化


	char *alloc::start_free = 0;
	char *alloc::end_free = 0;
	size_t alloc::heap_size = 0;
	//是一個鏈表數組
	alloc::obj *alloc::free_list[alloc::ENFreeLists::NFREELISTS] = {
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
	};

allocate 與 deallocate

這一部分是alloc 暴露的外部接口,通過找到當前free_list中第一個滿足要求內存塊大小的內存,從鏈表頭取出返回,如果是釋放內存就重新插到對應鏈表頭上。
注意這里的鏈表頭 表示的是大於多少K的節點 比如大於64卻小於512的內存塊

	void *alloc::allocate(size_t bytes) {
		if (bytes > EMaxBytes::MAXBYTES) {
			return malloc(bytes);//直接調用malloc
		}
		size_t index = FREELIST_INDEX(bytes);
		obj *list = free_list[index];
		if (list) {//此list還有空間給我們
			free_list[index] = list->next;
			return list;
		}
		else {//此list沒有足夠的空間,需要從內存池里面取空間
			return refill(ROUND_UP(bytes));
		}
	}
	void alloc::deallocate(void *ptr, size_t bytes) {
		if (bytes > EMaxBytes::MAXBYTES) {
			free(ptr);
		}
		else {
			size_t index = FREELIST_INDEX(bytes);
			obj *node = static_cast<obj *>(ptr);
			node->next = free_list[index];
			free_list[index] = node;
		}
	}
	void *alloc::reallocate(void *ptr, size_t old_sz, size_t new_sz) {
		deallocate(ptr, old_sz);
		ptr = allocate(new_sz);

		return ptr;
	}

內部的內存管理

refill負責對內存池中取出的對象做處理
chunk_alloc 負責從內存池中取出對應大小的內存塊

	//返回一個大小為n的對象,並且有時候會為適當的free list增加節點
	//假設bytes已經上調為8的倍數
	void *alloc::refill(size_t bytes) {
		size_t nobjs = ENObjs::NOBJS;
		//從內存池里取,會改變nobjs的值
		char *chunk = chunk_alloc(bytes, nobjs);
		obj **my_free_list = 0;
		obj *result = 0;
		obj *current_obj = 0, *next_obj = 0;

		if (nobjs == 1) {//取出的空間只夠一個對象使用
			return chunk;
		}
		else {//取出內存塊較大 需要進行回收
			my_free_list = free_list + FREELIST_INDEX(bytes);
			result = (obj *)(chunk);
			*my_free_list = next_obj = (obj *)(chunk + bytes);
			//將取出的多余的空間加入到相應的free list里面去
			for (int i = 1;; ++i) {
				current_obj = next_obj;
				next_obj = (obj *)((char *)next_obj + bytes);
				if (nobjs - 1 == i) {
					current_obj->next = 0;
					break;
				}
				else {
					current_obj->next = next_obj;
				}
			}
			return result;
		}
	}
	//假設bytes已經上調為8的倍數
	char *alloc::chunk_alloc(size_t bytes, size_t& nobjs) {
		char *result = 0;
		size_t total_bytes = bytes * nobjs;
		size_t bytes_left = end_free - start_free;

		if (bytes_left >= total_bytes) {//內存池剩余空間完全滿足需要
			result = start_free;
			start_free = start_free + total_bytes;
			return result;
		}
		else if (bytes_left >= bytes) {//內存池剩余空間不能完全滿足需要,但足夠供應一個或以上的區塊
			nobjs = bytes_left / bytes;
			total_bytes = nobjs * bytes;
			result = start_free;
			start_free += total_bytes;
			return result;
		}
		else {//內存池剩余空間連一個區塊的大小都無法提供
			size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
			if (bytes_left > 0) {//現有剩余內存加入內存池
				obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
				((obj *)start_free)->next = *my_free_list;
				*my_free_list = (obj *)start_free;
			}
			start_free = (char *)malloc(bytes_to_get);
			if (!start_free) {
				obj **my_free_list = 0, *p = 0;
				for (int i = 0; i <= EMaxBytes::MAXBYTES; i += EAlign::ALIGN) {
					my_free_list = free_list + FREELIST_INDEX(i);
					p = *my_free_list;
					if (p != 0) {
						*my_free_list = p->next;
						start_free = (char *)p;
						end_free = start_free + i;
						return chunk_alloc(bytes, nobjs);
					}
				}
				end_free = 0;
			}
			heap_size += bytes_to_get;
			end_free = start_free + bytes_to_get;
			return chunk_alloc(bytes, nobjs);
		}
	}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM