日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

stl源码剖析_STL源码剖析 阅读笔记(二)allocator

發布時間:2023/12/10 编程问答 57 豆豆
生活随笔 收集整理的這篇文章主要介紹了 stl源码剖析_STL源码剖析 阅读笔记(二)allocator 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、空間分配器 allocator

從使用上看,空間分配在任何語言的任何組件都不需要我們去過多關心,因為語言、組件的底層肯定都比較完整的做了這件事情。

從實現上看,學習 allocator 的原理在源碼學習中是首當其沖。因為沒有空間分配,則無從談起對象創建。這里說是空間分配,而不是內存分配,是因為也可以在內存之外的地方(如硬盤)分配空間。

分配器主要作用就是分配空間,根據規范,其需要實現一些接口,完成一些關于空間分配的功能。標準接口規范見附錄(一)。

本文會提到以下幾個方面:

  • SGI STL 分配器介紹
  • construct 和 destroy
    • destroy 接收指針和迭代器的方法
  • alloc 分配器
    • 一層分配器
    • 二層分配器 和 free-list
      • 分配
      • 釋放
      • 補充
  • 全局函數
    • uninitialized_copy
    • uninitialized_fill
    • uninitialized_fill_n

二、SGI STL 的 alloc

SGI STL 的分配器與眾不同,也與標準規范不同,其名稱是 alloc 而非 allocator,而且不接受任何參數。具體來說,想在程序中明確使用 SGI 分配器,不能寫std::allocator<int>,而要寫成std::alloc。

即使它不符合標準規范,也不會對我們使用造成任何影響,因為通常我們都使用缺省的分配器,而不會自己指定。而 STL 的每一個容器都指定缺省分配器為 alloc。

當然了,SGI 也定義了符合部分標準、名為 allocator 的分配器,但出于其效率原因,STL 從未使用它,也不推薦程序員使用。它只是把 ::operator new 和 ::operator delete 做了一層薄薄的封裝,沒有做優化。詳細代碼見附錄(二)。

下面詳細聊聊 SGI STL 實現的 alloc 。

一般而言,C++的內存分配和釋放操作如下:

class Foo {...}; Foo * pf = new Foo; // 分配內存,構造對象 delete pf; // 析構對象,釋放內存
  • new 內含兩步操作:(1)調用 ::operator new 分配內存(2)調用 Foo::Foo() 構造對象內容。
  • delete 內含兩步操作:(1)調用 Foo::~Foo() 析構對象(2)調用 ::operator delete 釋放內存。

為了精細分工,分配器將這兩個步驟分開做。內存分配由 alloc::allocate() 負責,內存釋放由 alloc::deallocate() 負責;對象構造由 ::construct() 負責,對象析構由 ::destroy() 負責。

// STL規定分配器 allocator 定義于 memory 中 #include <memory>// memory 中含有兩個文件 #include <stl_alloc.h> // 負責內存空間的分配和釋放,定義了 一級、二級分配器。 #include <stl_construct.h> // 負責對象內容的構造和析構,有 construct 和 destroy 方法。// memory 中還有一個文件 #include <stl_uninitialized.h> // 定義了一些全局函數,用來填充fill 或者 復制copy 大塊內存的數據 // 其中有如下方法 // un_initialized_copy() // un_initialized_fill() // un_initialized_fill_n() // 這些方法不屬于分配器的范疇,但與對象初值設置有關,對大規模元素初值設置很有幫助。 // 在效率上,最差會調用 construct,最佳會調用C的 memmove 進行內存移動。

(一)construct 和 destroy

對于對象的 construct 和 destroy 可以概括如下圖所示。其源碼見附錄(三)

  • construct 接收 指針p 和 初值value,會將value設置到p所指的空間上。
  • destroy 可以接收 指針、迭代器。
    • 基本類型指針:不做處理
    • 對象類型指針:調用析構函數
    • 迭代器:會判斷析構函數是否為 trivial destructor(無用的、沒必要的、無意義的析構函數)
      • 是:則不做處理
      • 否:調用 迭代器中每個元素的析構函數。

這里有個問題是,如何判斷是否為 trivial呢?

答案是:使用 __type_traits<T>::has_trivial_destructor() ,該函數會返回 __true_type 或 __false_type,前者代表是trivial,后者代表是有意義的。

該類的具體實現需要去研究下 traits,這里先不展開。

(二)STL alloc

<stl_alloc.h> 負責了對象構造前的空間分配和對象析構前的空間釋放,有下面幾個設計原則:

  • 向 system heap 申請空間
  • 考慮多線程狀態(為了將問題簡化,這里不討論多線程狀態)
  • 考慮內存不足的應變措施
  • 考慮過多小塊內存造成的碎片問題

C++ 的內存分配和釋放主要使用 ::operator new() 和 ::operator delete(),這兩個相當于C的 malloc() 和 free(),SGI 正是以 malloc 和 free 完成的內存分配和釋放。

SGI 設計了雙層分配器,如下圖所示:

  • 第一級直接使用 malloc 和 free
  • 第二級,當需求大于128 bytes 時,調用一級分配器;小于等于128 bytes 則調用二級分配器。

具體采用哪種分配器,需要看 __USE_MALLOC 是否被定義。定義了則用一級分配器,否則調用二級分配器。

SGI 為 alloc 提供了一個 simple_alloc 的接口封裝,使得外層使用時無需考慮內部具體用的一級還是二級。SGI STL 的容器都使用這個 simple_alloc 接口,而非直接使用 alloc。代碼見附錄(四)。

一級分配器的原理比較簡單,正常情況就是調用 malloc 和 free 做分配和釋放。當內存不夠時需要使用 oom_malloc,在該函數中,會循環調用一個 handler 來處理內存不足的情況。這個 handler 是需要自己指定的,如果沒有指定,則拋出 std::bad_alloc 異常。這個 handler 一般稱為 new-handler,在 《Effective C++》2e item7 中有特定的解決模式。

(三)STL 二級分配器

下面著重說說二級分配器。

二級分配器可以避免產生過多的小區塊,可以解決內存碎片和過多的額外開銷(系統需要多出來的空間管理內存,可以說是給系統“交稅”)。

二級分配器以內存池(memory pool)管理小于128 bytes 的內存,稱為次層分配(sub-allocation):先分配一大塊內存,組成一個自由鏈表(free-list),每次要取一定量內存時,從 free-list 中取;在用完后,分配器就歸還給 free-list。

分配器會維護 空間為 8、16、24、……、128 這16個 free-list,在分配小內存時,會向上取整(Round Up),尋找最近的 free-list。

free-list 節點結構是一個聯合體,該節點在free-list中時,內容是一個指向 下一個節點的指針,在客戶端使用時,是具體的數據。這樣一物二用,不會造成維護鏈表指針的內存浪費。這個技巧在強類型語言(Strong Typed)中如 Java 行不通,但在弱類型語言(Weak Typed)中如 C++十分常見。

union obj{union obj * free_list_link; char client_data[1]; // client use }

free-list 的實現技巧

次層分配中從 free list 分出內存的步驟 allocate 如下圖所示:

次層分配中釋放內存,往 free list 中歸還的步驟 deallocate 如下圖所示:

當 free-list 的空間用盡后,會觸發 refill 操作,重新給 free-list 補充 20個節點。refill 會調用 chunk_alloc,該函數中會做具體從內存池中取內存的操作。其過程如下所示。

簡而言之就是,先找自己(32找32),再找親友(64找32),實在不行就求助大家(96找32)。

三、內存基本處理工具

STL 定義了五個全局函數,除了前文提到的 construct 和 destroy,還有3個用來處理大塊內存的復制和移動的 unitialized_copy、uninitialized_fill、uninitialized_fill_n 分別對應高層次的函數 copy、fill、fill_n。

unitialized_copy 函數讓內存配置與對象構造行為分開。如果目標地址指向的空間都是未初始化區域,則會直接把源區域的對象產生復制品直接放到目標地址。STL 規范中要求該函數具有原子性,要么全部構造出來,要么全部不構造。

uninitialized_fill、uninitialized_fill_n 也和 unitialized_copy 類似。

這三個函數都會判斷 對象是否為 POD(Plain Old Data,標量 or 傳統 C 結構體),POD 會具有 trivial 函數,如果是 POD 則用最有效率的方法,如果非 POD 則用最安全的方法。過程大致如下所示。

附錄

(一)標準接口規范

根據 STL 規范,allocator 必須要實現以下接口。

(二)SGI allocator 源碼

下面是 SGI 實現的 allocator 全貌

(三)construct 和 destroy 源碼

(四)simple_alloc 和 vector

總結

以上是生活随笔為你收集整理的stl源码剖析_STL源码剖析 阅读笔记(二)allocator的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。