STL記憶體管理

2022-04-18 18:04:24 字數 3637 閱讀 8588

過年在家無事看了《stl原始碼剖析》,開學了將看過的東西總結一下,以防忘記。

先從stl的記憶體管理開始總結。掌管stl記憶體控制的是乙個叫空間介面卡(alloc)的東西。stl有兩個空間介面卡,sgi標準空間介面卡(allocate)和sgi特殊的空間介面卡(alloc),前者只是對c++的new和delete進行了簡單的封裝。下面主要介紹的是alloc空間介面卡。

一般而言,c++的記憶體配置和釋放操作是這樣的:

classfoo 

foo* pf = newfoo;

delete pf;

其中new操作符包含了兩個階段:1、operator new配置空間;2、呼叫foo建構函式構造內容。同理,delete操作符也包含呼叫析構函式和釋放空間兩個過程。stl記憶體管理參考了這種方法,將空間配置和構造內容分開。記憶體配置和記憶體釋放分別由alloc::allocate() 和 alloc::deallocate() 負責,物件構造和析構分別由alloc::construct() 和 alloc::destroy()負責。

下面先介紹construct() 和 destroy() 函式。

template inline void construct(t1* p, const t2&value)

template inline void destroy(t*pointer)

從**中可以看出,負責物件構造和析構的alloc::construct() 和 alloc::destroy()函式就是對建構函式t()和析構函式~t()的封裝。(注意:上面**並沒有涉及任何的記憶體配置與釋放,單純的呼叫建構函式和析構函式,這就是constructor()裡面為什麼用placement new 而不是 operator new 的原因。)另外,alloc過載第二個版本的alloc::destroy(),接受兩個iterator引數,用來批量地析構物件,並且用type_traits程式設計技法判斷物件是否有tirvial destructor來判斷是否需要顯示的呼叫destroy函式,以此來加快批量析構物件的速度。其中type_traits簡言之就是用泛化的手法提取物件的型別資訊,用來判斷該物件到底有沒有trivial destructor,trivial destructor現在你可以理解為沒有顯示地定義析構函式,例如系統內建的int型變數,後面等有時間再總結一下type_traits和iterator_traits的實現原理。

前面總結了記憶體配置的第二部分----物件的構造和析構,下面總結一下第一部分----空間的配置與釋放。

sgi以malloc()和free()完成記憶體配置與釋放。為了解決小型區塊所可能造成的記憶體破碎問題,sgi設計了雙層級配置器,第一級直接使用malloc()和free()配置和釋放記憶體空間,第二級配置器採用如下策略:當配置區塊超過128 bytes 時,視為足夠大,呼叫第一級配置器,當小於128 bytes 時,採用memory pool的整理方式。下面主要總結第二級配置器。

如前所述,sgi第二級配置器的策略為:如果區塊足夠大,超過128 bytes 時,就移交第一級配置器處理。當區塊小於128 bytes 時,則以memory pool管理----每次配置一大塊記憶體的時候,都對應維護乙個自由鍊錶(free-list)。下次若有相同大小的記憶體需求時,直接從free-list中取,當有記憶體被釋放時,**到free-list當中。第二級配置器維護16個free-list,分別管理8,16,24...128 bytes 大小的區塊,客戶端請求的區塊會自動調整到上述大小,如 20 bytes 調整到 24 bytes。下面是free-list的節點。

union obj

這裡之所以用union而不同struct,是為了節約空間,union中的變數共用同乙個儲存空間,obj裡面就儲存著乙個位址,指向下乙個可用區塊,而那個區塊的首個位元組就是obj,也可以說這個位址也指向下乙個obj。可以這麼理解:可用區塊的第乙個位元組構成了乙個鍊錶,維護了同樣大小的區塊,而第乙個區塊的位址就放在free-list中。當free-list裡面某個大小的區塊無可用區塊時,就由chunk_alloc() 函式向記憶體池請求空間。上述總結可能沒有那麼直觀,下面結合部分**具體解釋。由於**較多,故只挑選關鍵**附以自己的理解,全部**可參考《stl原始碼剖析》。注:原始碼中使用union的做法只是為了節約空間,但是現在記憶體大小已不是人們關心的主要問題,又加之union在實際使用中會出現各種奇怪問題,所以大部分情況下還是推薦有struct代替。

// __nfreelists == 16 表示配置器所維護的16個free_list; volatile表示不允許編譯器對free_list變數進行優化; free_list裡面放的是obj的位址,也就是說該大小下可使用的第乙個區塊。

static obj * volatile free_list[__nfreelists];

static void *allocate(size_t n)

// 找到16個當中合適的free_list

my_free_list = free_list +freelist_index(n);

result = *my_free_list;

if(result == 0)

*my_free_list = result -> free_list_link;// 這裡是理解的關鍵,其實result已經指向了第乙個可用區塊,區塊的第乙個位元組是下乙個可用區塊也是下乙個obj的位址,所以這句話就已經移除了第乙個可用區塊。

returnresult;

}

空間釋放函式deallocate()首先判斷是否大於128 bytes,大於交由第一級配置器處理,小於就找出對應的free_list將之收回。在理解了allocate()的**之後,deallocate()裡面的關鍵**也就不難理解了:

my_free_list = free_list +freelist_index(n);

q -> free_list_link = *my_free_list; // q指向要收回的區塊

*my_free_list = q;

重新填充 free_list 的 refill() 函式就是用chunk_alloc函式從記憶體池中取出新的空間(預設為20個某個大小的新區塊,如記憶體池空間不夠也可能小於20),並構造成煉表的形式掛在當前大小的free_list後面。在理解了新區塊的第乙個位元組為下一區塊以及下一obj的位址這一概念之後,refill()的**也不難理解,故不在下面貼**了。

前面一直提到的chunk_alloc()函式,它的工作是為free_list從記憶體池中取空間。它的邏輯為若記憶體池空間完全滿足需求,則為free_list配置20個新區塊,若記憶體池剩餘空間不能完全滿足需求,但足夠**乙個及以上的區塊,則為free_list配置力所能及的區塊數目,若記憶體池剩餘空間連乙個區塊大小都無法滿足,就先把記憶體池中的剩餘零頭分配給適當的free_list,然後釋放維護更大區塊的free_list中的取尚未用的區塊,將空間還給記憶體池,以供記憶體池為當前free_list配置空間,若,山窮水盡,到處都沒有記憶體了,那就只能求助於第一級配置器,如果他再搞不定,那就只能丟擲異常了。在理解前面**的基礎上,chunk_alloc的**也不難理解。

本章後面還介紹了五個stl的全域性函式,作用於未初始化的空間上,方便後面的容器實現。

stl的記憶體管理大體就是這樣,主要用了malloc 和 free對記憶體進行配置,等回頭看看tcmalloc對malloc 和 free 改進再總結一記吧。

STL 記憶體管理

stl有很多種allocator,根據c 的標準,stl的allocator把物件的申請和釋放分為四個步驟 1 申請記憶體空間,對應的函式是allocator allocate 2 執行建構函式,對應的函式是allocator construct 3 執行析構函式,對應的函式是allocator d...

STL記憶體管理

linux記憶體分配管理 實現自己的malloc stl是一套非常高效的c 庫,提到記憶體管理,怎麼能少了他呢,花了近一天的時間來剖析這個。stl記憶體分配分為兩級,為什麼分為兩級,就比如你為了買一根普通的皮帶,去漢正街批發市場,別人不一定賣給你 親身經歷 只有一次性購買大量的貨才會去批發市場,商家...

stl記憶體管理

stl提供了很多泛型容器,如vector,list和map。程式設計師在使用這些容器時只需關心何時往容器內塞物件,而不用關心如何管理記憶體,需要用多少記憶體,這些stl容器極大地方便了c 程式的編寫。例如可以通過以下語句建立乙個vector,它實際上是乙個按需增長的動態陣列,其每個元素的型別為int...