C++STL內存管理方法(g++版)


STL作為C++的經典作品,一直備受人們關注。本文主要介紹STL的內存管理策略。

早期的STL內存管理

第一次接觸STL源碼是看侯捷先生的《STL源碼剖析》,此書通俗易懂,剖析透徹,是極佳的STL分析教程。不過由於是在2002年出版的,所以內容有些陳舊,不過仍然具有參考價值。

現代g++的STL是由SGI版的STL演化而來。

正如侯捷先生書中所講,早期STL內存分配有兩種方法:malloc/realloc/free和內存池。默認的內存分配策略是內存池,下圖中代碼節選自stl_alloc.h頭文件:

由圖中代碼(代碼摘自可知SGI3.3版本的STL源碼),如果需要分配的內存容量大於128字節時便會轉而調用malloc/realloc/free版的內存分配方法。如果需要分配的內存大小小

於128字節便會調用內存池來滿足需求,這種策略增加了代碼的復雜性,但減少了內存碎片的問題。

malloc/realloc/free的方法

這種分配方法定義在stl_alloc.h文件的__malloc_alloc_template類模板中,它只是對malloc/realloc/free的簡單封裝,並增加了一些措施用於處理內存不足時的情況(不斷嘗試分配,

釋放,再分配,再釋放)。

內存池

在SGI3.3中,內存池就是用一個指針數組存儲指向一個個不同的固定大小的已事先分配好的鏈表頭節點的指針。這句話可能有點繞口,分開說就是:內存池的主體是一個指針數組,數組中存儲的每個

指針都指向一個鏈表,每個鏈表的節點大小都是固定的,這些鏈表的每個節點都是事先分配好的內存,這些內存供客戶取用。用圖來表示就是如下圖:

圖中的鏈表的每個節點是分開的,這是為了易於理解,真實情況是每個節點都是緊挨着的,因為每次分配一大塊內存(用malloc),然后按固定大小切分,並且用指針將它們連接起來。圖中只畫了7個

鏈表,真實情況是16個鏈表,各自管理着大小為8,16,24,32,40,48,56,64,72,80,88,96,104,112,128字節的區塊。當需要的內存大小與這16個大小不匹配那么就向上舍入至最接近的大小(這會不

會造成更小的內存碎片?)。

有興趣的可以看以下具體實現,在stl_alloc.h文件中的__default_alloc_template類中。從源碼可以看出,內存池的實現比較復雜,特別是內存的分配和鏈表的構造。

現在的STL內存管理

現在的STL內存管理與早期的有很大不同,以g++5.4為例。從g++3.4開始,STL改變了原來的內存分配策略,默認的內存分配方式改為new/delete。在Linux上查看當前系統的STL內存分配源代碼的

順序為:bits/allocator.h -> bits/c++allocator.h -> ext/new_allocator.h。其中allocator.h是對底層內存分配器的封裝,供其他STL組件直接調用;bits/c++allocator.h中只

有一行代碼:

template<typename _Tp>
using __allocator_base = __gnu_cxx::new_allocator<_Tp>;

它定義了底層內存分配器為new_allocatorext/new_allocator.h是底層分配器的定義文件,它定義了new_allocator類,這個類只是對new/delete的簡單封裝。

由於容器每次需要內存時調用new,釋放內存時調用delete,所以這種內存分配方法會比內存池要。但是其優勢是在各種硬件和操作系統甚至大的集群中也能正確工作。另一種方法是使用
分配器中的緩存,這種額外的機制有多種實現形式:位圖索引,一個以2的指數級成倍增加的桶;或者是一個簡單的固定大小的緩沖池。分配器中的緩沖在一個程序的多個容器中共享。使用這些技術的
bitmap_allocatorpool_allocatormt_alloc。由於不同的實現,不同的操作系統和不同的編譯環境,擴展緩存分配器可能會非常棘手。特別是,內存池創建和析構的順序可能很難確
定,當和插件一起使用或者在內存中裝載和卸載共享對象時可能會產生問題。

以上內容節選自gcc關於內存分配的手冊頁中的說明,由此可見,gcc放棄使用繼承自sgi的內存池而使用new/delete是為了降低復雜度和增加可靠性。

gcc定義了幾種內存分配方法:

  1. new_allocator

    new/delete的簡單封裝,也是各種順序容器和關聯容器的默認內存分配器。

  2. malloc_allocator

    malloc/free的簡單封裝,增加了對out-of-memory的處理。

  3. array_allocator

    允許使用通過構造std::array對象分配的現有全局或外部存儲來分配已知和固定大小的內存。通過使用這個分配器,可以使用固定大小的容器(包括std::string),而無需調用newdelete

    此功能允許使用STL抽象,無需運行時復雜性或開銷,即使在諸如程序啟動的情況下。

  4. debug_allocator

    圍繞任意分配器的包裝器A.它將稍微增加大小的請求傳遞給A,並使用額外的內存來存儲大小信息。 當指針傳遞給deallocate()時,檢查存儲的大小,並使用assert()來保證它們匹配。

  5. throw_allocator

    包括內存跟蹤和標記能力以及在可配置的時間間隔拋出異常。

  6. __pool_alloc

    一個高性能,單池分配器。可重用的內存在這種類型的相同實例化之間共享。當它的內存用完時它調用通過運算符new獲取新的內存。如果客戶容器請求大於某個閾值大小的內存塊,則繞過內存池,並且將分配/釋放請求直接傳遞給new

  7. __mt_alloc

    一個高性能固定大小的分配器。

  8. bitmap_allocator

    一個高性能的分配器,使用位圖來跟蹤和標記使用和未使用的內存。

參考資料:

  1. 《STL源碼剖析》侯捷 著 2002年

  2. gcc手冊頁


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2020 ITdaan.com