1、概述
以STL運用的角度而言,空間配置器是最不需要介紹的,它總是藏在一切組件的背后,默默工作。整個STL的操作對象都存放在容器之中(vertor、list),而容器一定需要配置空間以放置資料,這就是空間配置器的作用。
雖然STL提供了讓我們自定義空間配置器的接口,但是不建議自己定義,因為標准提供的空間配置器是安全的,且效率也不錯的。所以我們使用時,一般都會使用默認的配置器。如下:
template <class T, class Alloc = allocator<T> >
class vector {};
vect<int> vec; //這里只傳入int類型,使用默認的空間配置器
- 1
- 2
- 3
- 4
下面的空間配置器是按照SGI 版本的STL進行講解的,但是STL的原理是通的。
2、空間配置器的內存分配和釋放
通過前面整理C++ new和delete的詳解,我們知道C++內存配置操作和釋放操作是這樣的:
class Foo {...};
Foo* pf = new Foo; //配置內存,然后構造對象
delete pf; //將對象析構,然后釋放內存
- 1
- 2
- 3
這其中的 new 內含兩個階段操作:1、調用operator new 配置內存。2、調用構造函數,構造對象內容
delete也內含兩個階段操作:1、調用析構函數。2、調用operator delete 釋放內存。
為了精密分工,STL 將這兩個階段操作區分開來。內存配置操作由 成員函數 alloccate() 負責,內存釋放由 deallcate() 負責;對象構造由 construct() 負責,對象析構則由 destroy() 負責。
在內存分配的過程中,會有幾個問題需要考慮。
1、小塊內存帶來的內存碎片問題。
2、小塊內存頻繁申請釋放帶來的性能問題。
為了解決這些問題,SGI STL設計了 雙層級配置器,也就是第一級配置器和第二級配置器。第一級配置器直接使用 malloc() 和 free() ,第二級配置器則視情況采用不同的策略:當配置區塊超過128 bytes 時,視之為 “足夠大”,便調用第一級配置器;當配置區塊小於 128 bytes 時,視之為 “過小” ,為了降低額外負擔,便采用復雜的 內存池 管理方式。
3、第一級配置器
第一級配置器的流程如下:
SGI的第一級配置器以 malloc(), free(), realloc() 等C函數執行實際的內存配置、釋放、重配置操作。當 malloc 或者 realloc 調用不成功后,改調用 oom_malloc() 和 oom_realloc() 。后兩者都有內循環,不斷調用“內存不足處理例程”,期望在某次調用之后,獲得足夠的內存。但如果“內存不足處理例程”未被客戶端設定,則直接拋出 bad_alloc 異常,或者終止程序。
注意:設計內存不足處理例程是客戶端的責任,設定內存不足處理例程也是客戶端的責任。
4、第二級配置器
二級配置器使用內存池+自由鏈表的形式避免了小塊內存帶來的碎片化,提高了分配的效率,提高了利用率。它是用一個16個元素的自由鏈表(free_list)來管理的,每個位置的內存大小都是8的倍數,分別為:8、16、24、32、40、48、56、64、72、80、88、96、104、112、120、128。
free_list的節點結構如下:
union obj
{
union obj* free_list_link;
char client_data[1];
};
- 1
- 2
- 3
- 4
- 5
使用union是為了節省內存,這樣每個節點就不需要額外的指針。
內存池與自由數組 free_list 之間的關系如下圖所示:其中free_list的第一個元素指向 8個字節的空間,8個字節的空間我給分配了10個。free_list的最后一個元素指向128個字節的空間,此空間我給分配了4個。
free_list管理的是內存池中已經分配給 free_list 且尚未使用的內存,如果系統想從free_list中拿一8字節內存,則直接從free_list[0]中彈出頂部第一個元素,然后頂部后移。
4.1、二級配置器內存分配
主要分為四種情況:
1、free_list列表中有空余內存。如果申請3個字節的內存,則所需空間大小提升為8的倍數,然后去 free_list 中查找相應的鏈表,如果 free_list[i] 不為空,則返回第一個元素,然后把頭指針往后移。
2、free_list 列表中沒有空余,但內存池不為空。首先檢驗內存池中的大小是不是比申請的內存大,比如申請20*8的內存,如果足夠,則分配相應內存,將其中一個分配給用戶使用,其它的掛在相應的 free_list 中。如果內存池不夠大,只夠幾個內存分配,則就分配這幾個,把相應的數據返回。如果連一個都不夠則執行第三中情況。
3、free_list列表中沒有空余,內存池也不夠。調用malloc重新分配內存,分配時會多分配一倍的內存,把相應的內存掛到free_list下,剩余的放到內存池中。
4、free_list列表中沒有空余,內存池也不夠,malloc也失敗。則調用一級空間配置器,里面會有循環處理,或者拋出異常。
4.2、二級配置器內存回收
當用戶從二級空間配置器中申請的內存被釋放時,二級空間配置器將回收的內存插入到對應的 free_list 中。其流程如下:
4.4、總結
我們知道,引入相對復雜的空間配置器,主要源自兩點:
1、頻繁使用malloc、free開辟釋放小塊內存帶來的性能效率的低下
2、內存碎片問題,導致不連續內存不可用的浪費
引入兩層配置器幫我們解決了以上的問題,但是也帶來一些問題:
1、內存碎片的問題,自由鏈表所掛區塊都是8的整數倍,因此當我們需要非8倍數的區塊,往往會導致浪費。
2、我們並沒有釋放內存池中的區塊。釋放需要到程序結束之后。這樣子會導致自由鏈表一直占用內存,其它進程使用不了。
STL下的空間配置器分位兩級,他們沒有高低之分,只有一個條件,當用戶所需要的的內存大小: