STL空間配置器
空間配置器——《STL源碼剖析》chapter2
2.1 STL標準規定的空間配置器的標準接口
- STL規定的空間配置器的名稱是
allocator
- 包含下列typedef:
value_type
pointer
/const_pointer
reference
/const_reference
size_type
difference_type
- 包含
allocator::rebind
- 包含成員函數:
allocate()
,deallocate()
,construct()
,destroy()
。這4個全是成員函數,作用分別是:配置空間,釋放配置空間,構造對象,析構對象。 - SGI實現的STL未遵守STL標準的規定,使用的是一個具有次配置能力(sub-allocation)的特殊配置器。
2.2 具有次配置能力的SGI STL空間配置器
2.2.1 SGI也實現了STL標準規定的std::allocator
但是不建議用(只是對operator new
和operator delete
做了簡單封裝)
2.2.2 SGI中的特殊配置器名字叫std::alloc
-
class Foo{...}; Foo *pf = new Foo; delete pf;
上面的new算式包含兩階段操作:1. 調用
operator new
配置內存,2. 調用Foo::Foo()
構造函數對象。delete算式也包含兩階段操作:1. 調用
Foo::~Foo()
將對象析構,2. 調用operator delete
釋放內存。 -
爲了精密分工,STL配置器將這兩階段操作區分開來。內存的配置和釋放由
alloc
的成員函數alloc::allocate()
和alloc::deallocate()
來負責,對象的構造和析構由全局函數::construct()
和::destroy()
來負責。 -
標準規定,配置器定義於頭文件
<memory>
中,<memory>
包含兩個文件:#include<stl_alloc.h>
(這裏面定義了一二級配置器)和#include<stl_construct.h>
(這裏定義了全局的::construct()
和::destroy()
)。
2.2.3 構造和析構基本工具::construct()
和::destroy()
::construct()
:利用placement new
在傳入的地址上構造對象。::destroy()
:有多個版本。1. 接受一個指針類型:直接調用指針指向對象的析構函數;2. 接受兩個迭代器,需要先判斷類型的析構函數是否trival(無關緊要),如果trival就什麼都不做,如果不trival就遍歷,依次調用上一個版本的destroy()
。
2.2.4 空間的配置與釋放std::alloc
- 考慮小區塊造成的內存破碎問題,SGI設計了雙層級配置器。
- 第一級:直接使用
malloc()
和free()
。 - 第二級:
- 如果配置區塊大小超過128byte,直接用
malloc()
和free()
。 - 如果配置區塊大小不足128byte,爲降低額外負擔,採用複雜的memory pool整理方式。
- 如果配置區塊大小超過128byte,直接用
2.2.5 第一級配置器malloc_alloc
-
template<int inst> class __malloc_alloc_template__{...}; //別名 typedef __malloc_alloc_template__<0> malloc_alloc;
-
allocate()
直接使用malloc()
。 -
deallocate()
直接使用free()
。 -
模擬C++的
set_new_handler()
以處理內存不足的狀況。
2.2.6 第二級配置器
-
sub-allocation:每次配置一大塊內存,並維護對應之自由鏈表(free-list)。下次若再有相同大小的內存需求,就直接從free-lists中拔出。如果客端釋還小額區塊,就由配置器回收到free-list中。
-
第二級配置器會自動將內存需求量上調至8的倍數,並維護16個free-lists(分別管理8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小額區塊)
-
free-list節點的結構
union obj{ union obj * free_list_link; char client_data[1]; };
使用
union
,一物二用,每個節點不需要額外的指針來維護鏈表。
2.2.7 空間配置函數allocate()
-
源碼:
// n must be > 0 static void * allocate(size_t n) { obj * volatile * my_free_list; obj * result; // 大於128就調用第一級配置器 if(n>(size_t)__MAX_BYTES){ return (malloc_alloc::allocate(n)); } //尋找16個free-lists中適當的一個 my_free_list = free_list + FREELIST_INDEX(n); result = *my_free_list; if(result == 0){ //沒有可用free-list,準備重新填充free-list void *r = refill(ROUND_UP(n)); return r; } //調整free-list *my_free_list = result->free_list_link; return (result); }
-
第一步:找打n對應的free-list;第二步:將result指向這個free-list的開頭;第三步:調整free-list。二三兩步就是將一個區塊從free-list中摘下來。
-
如果沒有可用的free-list,調用
refill()
重新填充free-list
2.2.8 空間釋放函數deallocate()
-
源碼:
//p 不可以是0 static void deallocate(void *p, size_t n) { obj *q = (obj*)p; obj *volatile * my_free_list; // 大於128就調用第一級配置器 if (n>(size_t)__MAX_BYTES){ malloc_alloc::deallocate(p,n); return; } my_free_list = free_list + FREELIST_INDEX(n); //調整free-list,回收區塊 q->free_list_link = *my_free_list; *my_free_list = q; }
2.2.9 重新填充refill()
-
當free-list沒有可用區塊的時候,
refill()
從內存池中獲取新的空間。 -
缺省獲得20個區塊大小的空間,若內存池空間不足,實際獲取的區塊可能不足20。
-
源碼
//假設n已經上調至8的倍數 template <bool threads, int inst> void* __default_alloc__template<threads,inst>::refill(size_t n) { int nobjs = 20; //調用chunk_alloc(),嘗試獲得nobjs個區塊作爲free-list的新節點 //注意參數nobjs是pass by reference char* chunk = chunk_alloc(n,nobjs); obj * volatile * my_free_list; obj * result; obj * current_obj, * next_obj; int i; //如果只有一個區塊,這個區塊就分配給調用者用,free-list無新節點 if(n==1) return (chunk); //否則準備調整free-list my_free_list = free_list + FREELIST_INDEX(n); //以下在chunk空間內建立free-list result = (obj*) chunk; //這一塊準備返回給客端 //以下引導free-list指向新配置的空間 *my_free_list = next_obj = (obj*)(chunk+n); //以下將free-list的各節點串聯起來 for(i=1;;i++){ current_obj = next_obj; next_obj = (obj*)((char*)next_obj+n); if(nobjs-1 == i){ current_obj->free_list_link = 0; break; }else{ current_obj->free_list_link = next_obj; } } return (result); }
2.2.10 內存池
-
chunk_alloc()
從內存池中取空間給free-list使用,在refill()
中被調用。 -
template <bool threads, int inst> char* __default_alloc_template__<threads,inst>:: chunk_alloc(size_t size, int& nobjs) { //··· }
-
chunk_alloc()
的格式如上面所示,根據傳進來的size
和nobjs
分配空間。 -
首先在
__default_alloc_template__
類中有兩個指針:start_free
和end_free
,兩個指針都是static char*
格式,分別指向內存池起始位置和內存池結束位置。 -
chunk_alloc()
以end_free
-start_free
判斷內存池的水量,如果水量充足,就直接調出20個區塊返回給free-list。 -
如果水量不足以提供20個區塊,但還足夠提供一個以上的區塊,就撥出這不足20個區塊的空間出去。此時pass by reference的
nobjs
參數將被修改爲實際能提供的區塊數。 -
如果內存池連一個區塊空間都不夠了,就調用
malloc
從heap中配置內存,爲內存池注入源頭活水以應付需求。 -
萬一山窮水盡,整個system heap空間都不夠了,
malloc
失敗,chunk_alloc
就四處尋找有無“尚有未用區塊,且區塊足夠大”之free-list。找到了就挖一塊交出,找不到就調用第一級配置器(寄希望於第一級配置器的out-of-memory處理機制)。如果可以就成功,否則就拋出bad_alloc
異常。