STL提供了很多泛型容器,如vector,list和map。程序員在使用這些容器時只需關心何時往容器內塞對象,而不用關心如何管理內存,需要用多少內存,這些STL容器極大地方便了C++程序的編寫。例如可以通過以下語句創建一個vector,它實際上是一個按需增長的動態數組,其每個元素的類型為int整型:
stl::vector<int> array;
擁有這樣一個動態數組后,用戶只需要調用push_back方法往里面添加對象,而不需要考慮需要多少內存:
array.push_back(10);
array.push_back(2);
vector會根據需要自動增長內存,在array退出其作用域時也會自動銷毀占有的內存,這些對於用戶來說是透明的,stl容器巧妙的避開了繁瑣且易出錯的內存管理工作。
隱藏在這些容器后的內存管理工作是通過STL提供的一個默認的allocator實現的。當然,用戶也可以定制自己的allocator,只要實現allocator模板所定義的接口方法即可,然后通過將自定義的allocator作為模板參數傳遞給STL容器,創建一個使用自定義allocator的STL容器對象,如:
stl::vector<int, UserDefinedAllocator> array;
大多數情況下,STL默認的allocator就已經足夠了。這個allocator是一個由兩級分配器構成的內存管理器,當申請的內存大小大於128byte時,就啟動第一級分配器通過malloc直接向系統的堆空間分配,如果申請的內存大小小於128byte時,就啟動第二級分配器,從一個預先分配好的內存池中取一塊內存交付給用戶,這個內存池由16個不同大小(8的倍數,8~128byte)的空閑列表組成,allocator會根據申請內存的大小(將這個大小round up成8的倍數)從對應的空閑塊列表取表頭塊給用戶。
這種做法有兩個優點:
1)小對象的快速分配。小對象是從內存池分配的,這個內存池是系統調用一次malloc分配一塊足夠大的區域給程序備用,當內存池耗盡時再向系統申請一塊新的區域,整個過程類似於批發和零售,起先是由allocator向總經商批發一定量的貨物,然后零售給用戶,與每次都總經商要一個貨物再零售給用戶的過程相比,顯然是快捷了。當然,這里的一個問題時,內存池會帶來一些內存的浪費,比如當只需分配一個小對象時,為了這個小對象可能要申請一大塊的內存池,但這個浪費還是值得的,況且這種情況在實際應用中也並不多見。
2)避免了內存碎片的生成。程序中的小對象的分配極易造成內存碎片,給操作系統的內存管理帶來了很大壓力,系統中碎片的增多不但會影響內存分配的速度,而且會極大地降低內存的利用率。以內存池組織小對象的內存,從系統的角度看,只是一大塊內存池,看不到小對象內存的分配和釋放。
實現時,allocator需要維護一個存儲16個空閑塊列表表頭的數組free_list,數組元素i是一個指向塊大小為8*(i+1)字節的空閑塊列表的表頭,一個指向內存池起始地址的指針start_free和一個指向結束地址的指針end_free。空閑塊列表節點的結構如下:
union obj {
union obj *free_list_link;
char client_data[1];
};
這個結構可以看做是從一個內存塊中摳出4個字節大小來,當這個內存塊空閑時,它存儲了下個空閑塊,當這個內存塊交付給用戶時,它存儲的時用戶的數據。因此,allocator中的空閑塊鏈表可以表示成:obj* free_list[16];
allocator分配內存的算法如下:
算法:allocate
輸入:申請內存的大小size
輸出:若分配成功,則返回一個內存的地址,否則返回NULL
{
if(size大於128){ 啟動第一級分配器直接調用malloc分配所需的內存並返回內存地址;
else {
將size向上round up成8的倍數並根據大小從free_list中取對應的表頭free_list_head;
if(free_list_head不為空){
從該列表中取下第一個空閑塊並調整free_list;
返回free_list_head;
}
} else {
調用refill算法建立空閑塊列表並返回所需的內存地址;
}
}
重新填充算法:
算法: refill
輸入:內存塊的大小size
輸出:建立空閑塊鏈表並返回第一個可用的內存塊地址
{
調用chunk_alloc算法分配若干個大小為size的連續內存區域並返回起始地址chunk和成功分配的塊數nobj;
if(塊數為1)直接返回chunk;
else{
開始在chunk地址塊中建立free_list;
根據size取free_list中對應的表頭元素free_list_head;
將free_list_head指向chunk中偏移起始地址為size的地址處, 即free_list_head=(obj*)(chunk+size);
再將整個chunk中剩下的nobj-1個內存塊串聯起來構成一個空閑列表;
返回chunk,即chunk中第一塊空閑的內存塊;
}
}
塊分配算法
算法:chunk_alloc
輸入:內存塊的大小size,預分配的內存塊塊數nobj(以引用傳遞)
輸出:一塊連續的內存區域的地址和該區域內可以容納的內存塊的塊數
{
計算總共所需的內存大小total_bytes;
if(內存池中足以分配,即end_free - start_free >= total_bytes) {
則更新start_free;
返回舊的start_free;
}
else if(內存池中不夠分配nobj個內存塊,但至少可以分配一個){
計算可以分配的內存塊數並修改nobj;
更新start_free並返回原來的start_free;
}
else { //內存池連一塊內存塊都分配不了
先將內存池的內存塊鏈入到對應的free_list中后;
調用malloc操作重新分配內存池,大小為2倍的total_bytes加附加量,start_free指向返回的內存地址;
if(分配不成功) {
if(16個空閑列表中尚有空閑塊)
嘗試將16個空閑列表中空閑塊回收到內存池中再調用chunk_alloc(size, nobj);
else
調用第一級分配器嘗試out of memory機制是否還有用;
}
}
更新end_free為start_free+total_bytes,heap_size為2倍的total_bytes;
調用chunk_alloc(size,nobj);
}
假設這樣一個場景,free_list[2]已經指向了大小為24字節的空閑塊鏈表,如圖1所示,當用戶向allocator申請21字節大小的內存塊時,allocaotr會首先檢查free_list[2]並將free_list[2]所指的內存塊分配給用戶,然后將表頭指向下一個可用的空閑塊,如圖2所示。注意,當內存塊在鏈表上是,前4個字節是用作指向下一個空閑塊,當分配給用戶時,它是一塊普通的內存區。
圖1 某時刻allocator的狀態
圖2 分配24字節大小的內存塊
http://blog.csdn.net/adcxf/article/details/6437880