STL學習——淺談空間配置器

1. 什麼是空間配置器

        空間配置器,顧名思義就是爲各個容器高效的管理空間(空間的申請與回收)的,在默默地工作。雖然在常規使用STL時,可能用不到它,但站在學習研究的角度,學習它的實現原理對我們有很大的幫助。

2. 爲什麼需要空間配置器

在這裏插入圖片描述
        前面在模擬實現vector、list、map、unordered_map等容器時,所有需要空間的地方都是通過new申請的,雖然代碼可以正常運行,但是有以下不足之處:

1.空間申請與釋放需要用戶自己管理,容易造成內存泄漏
2.頻繁向系統申請小塊內存塊,容易造成內存碎片
3.頻繁向系統申請小塊內存,影響程序運行效率
4.直接使用malloc與new進行申請,每塊空間前有額外空間浪費
5.申請空間失敗怎麼應對
6.代碼結構比較混亂,代碼複用率不高
7.未考慮線程安全問題

因此需要設計一塊高效的內存管理機制。

3. SGI-STL空間配置器實現原理

        以上提到的幾點不足之處,最主要還是:頻繁向系統申請小塊內存造成的。那什麼纔算是小塊內存?SGI-STL以128作爲小塊內存與大塊內存的分界線,將空間配置器其分爲兩級結構,一級空間配置器處理大塊內存,二級空間配置器處理小塊內存。這篇文章會介紹一級空間配置器的實現,一級與STL容器的結合。二級空間配置器後續會補充。

        一級空間配置器原理非常簡單,直接對malloc與free進行了封裝,並增加了C++中set_new_handle思想

簡單介紹一下set_new_handler:

        set_new_handler允許客戶指定一個函數,在內存分配無法獲得滿足時被調用。
C/C++內存分配採用new和delete。在new申請內存時,可能會遇到的一種情況就是,內存不夠了,這時候會拋出out of memory的異常。有的時候,我們希望能夠調用自己定製的異常處理函數,這就是本條款要說的。在聲明於的一個標準程序庫中,有如下的接口:

1 namespace std
2 {
3     typedef void (*new_handler)();
4     new_handler set_new_handler(new handler p) throw();
5 }

        注意這裏面typedef了一個函數指針new_handler,它指向一個函數,這個函數的返回值爲void,形參也是void。set_new_handler就是將new_handler指向具體的函數,在這個函數裏面處理out of memory異常(函數末尾的throw()表示它不拋出任務異常),如果這個new_handler爲空,那麼這個函數沒有執行,就會拋出out of memory異常。

 1 void MyOutOfMemory()
 2 {
 3     cout << "Out of memory error!" << endl;
 4     abort();
 5 }
 6 
 7 int main()
 8 {
 9     set_new_handler(MyOutOfMemory);
10     int *verybigmemory = new int[0x1fffffff];
11     delete verybigmemory;
12 }

        這裏預先設定好new異常時調用的函數爲MyOutOfMemory,然後故意申請一個很大的內存,就會走到MyOutOfMemory中來了。

注意:第一級配置器的實質是使用了malloc、realloc、free。並處理了內存不足(請求內存失敗)的情況,因爲::operator new提供了set_new_handler操作,使用戶可以自己定製發生內存不足時的處理策略。SGI STL並不能使用C++的set_new_handler,所以單獨實現了這個功能。

下面給出一級空間配置器的實現

template<int inst>
class _malloc_alloc_template
{
	/* oom_alloc爲靜態函數成員,用於處理malloc時的內存不足問題
	   oom_realloc爲靜態函數成員,用於處理realloc時的內存不足問題
	   _malloc_alloc_handler爲靜態數據成員,爲void(*)()類型的函數指針,用於
	   //用戶自己制定內存分配策略
	*/
	static void * oom_malloc(size_t);//out_of_memmory malloc
	static void * oom_realloc(void *, size_t);
	static void(*_malloc_alloc_oom_handler)();
public:
	static void * allocate(size_t n)
	{
		void * result = malloc(n);//請求內存
		if (result == nullptr)//如果內存不足
			result=oom_malloc(n);//調用oom_malloc
		return result;
	}
	static void * reallocate(void * p, size_t n)
	{
		void *result = realloc(n);
		if (result == nullptr)
			result = oom_realloc(p, n);
		return result;
	}
	static void deallocate(void * p)
	{
		//使用free函數釋放p地址後所分配的內存塊
		free(p);
	}
 
	/*此靜態成員函數接受一個void(*)()類型的函數指針作爲參數,返回
	void(*)()類型的函數指針。其作用爲用用戶自己定製的內存調度方法替換
	_malloc_alloc_handler,由此實現類似C++的set_new_handler方法。
	*/
 
	static void(* set_malloc_handler(void(*f)()))()
	{
		void(*old)() = _malloc_alloc_oom_handler;
		_malloc_alloc_oom_handler = f;
		return old;
	}
};
 
template<int inst>
void(*_malloc_alloc_template<inst>::_malloc_alloc_oom_handler)() = 0;
 
template<int inst>
void * _malloc_alloc_template<inst>::oom_malloc(size_t n)
{
	void(*my_oom_handler)();
	void * result;
	//無限循環,直至成功分配內存或用戶沒有定製內存分配策略
	for (;;)
	{
		my_oom_handler = _malloc_alloc_oom_handler;
		if (my_oom_handler == nullptr)//如果用戶沒有定製內存分配策略
			exit(1);
		(*my_oom_handler)();//使用用戶定製的方法
		result = malloc(n);
		if (result)
			return result;
	}
}


template<int inst>
void * _malloc_alloc_template<inst>::oom_realloc(void * p, size_t n)
{
	//此函數的設計思路與oom_malloc如出一轍
	void(*my_oom_handler)();
	void * result;
	for (;;)
	{
		my_oom_handler = _malloc_alloc_oom_handler;
		if (my_oom_handler == nullptr)
			exit(1);
		(*my_oom_handler)();
		result = realloc(p,n);
		if (result)
			return result;
	}
}


4.空間配置器的封裝和STL容器和空間配置器的結合

SGI兩層配置器

#ifdef __USE_MALLOC

typedef malloc_alloc alloc;
typedef malloc_alloc single_client_alloc;

#else
 
 // 二級空間配置器定義
#endif

        由於以上的問題,SGI設計了兩層的配置器,也就是第一級配置器和第二級配置器。同時爲了自由選擇,STL又規定了 __USE_MALLOC 宏,如果它存在則直接調用第一級配置器,不然則直接調用第二級配置器。SGI未定義該宏,也就是說默認使用第二級配置器。

        通過預處理命令在第一級或第二級配置器之間選擇一個命名爲alloc,再爲類模板設置第二個模板類型參數:

template<typename T,class Alloc=alloc>
class vector
{
	...
};

        因爲我們將_malloc_alloc_template或_default_alloc_template設置爲底層的空間配置器,所以我們還需要設計一個包裝函數使其符合常規的空間配置器的使用方式,還可以對底層實現進行更好的封裝:


/*
simple_alloc爲底層的內存分配類的外部包裝,其成員全部調用_malloc_alloc_template
的成員
*/
template<typename T,class Alloc=alloc>
class simple_alloc
{
public:
	static T * allocate(void)
	{
		return (T *)Alloc::allocate(sizeof(T));
	}
	static T * allocate(size_t n)
	{//此allocate接受一個指定對象個數的參數n
		return n == 0 ? nullptr : (T *)Alloc::allocate(sizeof(T)*n);
	}
	static void deallocate(T * p)
	{
		Alloc::deallocate(p, sizeof(T));
	}
	static void deallocate(T * p, size_t n)
	{
		if (n != 0)
			Alloc::deallocate(p);
	}
};

這樣我們便可以這樣使用它們:

template<typename T,class Alloc=alloc>
class vector
{
	typedef T value_type;
protected:
	typedef simple_alloc<value_type, Alloc> data_allocator;
	...
		void deallocate()
	{
		if (...)
			data_allocator::deallocate(start, end_of_storge - start);
	}
	...
};

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章