STL源碼分析之第二級配置器

前言

第一級是直接調用malloc分配空間, 調用free釋放空間, 第二級三就是創建一個內存池, 小於128字節的申請都直接在內存池申請, 不直接調用mallocfree. 本節分析第二級空間配置器, STL將第二級配置器設置爲默認的配置器, 因此只要一次申請的空間不超過128字節就默認在內存池中申請空間, 超過纔會調用第一級配置器.code

第二級配置器

首先先來介紹3個常量.對象

  1. __ALIGN : 以8字節進行對齊
  2. __MAX_BYTES : 二級分配器最大分配的內存大小
  3. __NFREELISTS : 128字節能分配的的鏈表個數, 而且從每一個鏈表保存的內存大小都是8的倍數, 並且都比前一個大8字節, 也就是分別是8, 16, 32...128字節
// 二級配置器
enum {__ALIGN = 8}; // 設置對齊要求. 對齊爲8字節, 沒有8字節自動補齊
enum {__MAX_BYTES = 128};   // 第二級配置器的最大一次性申請大小, 大於128就直接調用第一級配置器
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};  // 鏈表個數, 分別表明8, 16, 32....字節的鏈表

再介紹一個宏操做, 這是進行對齊操做, 將不滿8的倍數的填充成8的倍數.接口

static size_t FREELIST_INDEX(size_t bytes) \
{\
    return (((bytes) + ALIGN-1) / __ALIGN - 1);\
}

從allocate先切入分析

  1. 先判斷申請的字節大小是否是大於128字節, 是, 則交給第一級配置器來處理. 否, 繼續往下執行
  2. 找到分配的地址對齊後分配的是第幾個大小的鏈表.
  3. 得到該鏈表指向的首地址, 若是鏈表沒有多餘的內存, 就先填充鏈表.
  4. 返回鏈表的首地址, 和一塊能容納一個對象的內存, 並更新鏈表的首地址
static void * allocate(size_t n)
{
      obj * __VOLATILE * my_free_list;
      obj * __RESTRICT result;
    
      if (n > (size_t) __MAX_BYTES) 
      {
        return(malloc_alloc::allocate(n));
      }
      my_free_list = free_list + FREELIST_INDEX(n);
      result = *my_free_list;
      if (result == 0)  // 沒有多餘的內存, 就先填充鏈表.
      {
        void *r = refill(ROUND_UP(n));
        return r;
      }
      *my_free_list = result -> free_list_link;
      return (result);
};

refill內存填充.內存

  1. 向內存池申請空間的起始地址
  2. 若是隻申請到一個對象的大小, 就直接返回一個內存的大小, 若是有更多的內存, 就繼續執行
  3. 從第二個塊內存開始, 把從內存池裏面分配的內存用鏈表給串起來, 並返回一個塊內存的地址給用戶
// 內存填充
template <bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
    int nobjs = 20;
    char * chunk = chunk_alloc(n, nobjs);             // 向內存池申請空間的起始地址
    obj * __VOLATILE * my_free_list;
    obj * result;
    obj * current_obj, * next_obj;
    int i;

    // 若是隻申請到一個對象的大小, 就直接返回一個內存的大小
    if (1 == nobjs) return(chunk);
    my_free_list = free_list + FREELIST_INDEX(n);

    // 申請的大小不僅一個對象的大小的時候
    result = (obj *)chunk;
    // my_free_list指向內存池返回的地址的下一個對齊後的地址
    *my_free_list = next_obj = (obj *)(chunk + n);
    // 這裏從第二個開始的緣由主要是第一塊地址返回給了用戶, 如今須要把從內存池裏面分配的內存用鏈表給串起來
    for (i = 1; ; i++) 
    {
        current_obj = next_obj;
        next_obj = (obj *)((char *)next_obj + n);
        if (nobjs - 1 == i) 
        {
            current_obj -> free_list_link = 0;
            break;
        } 
        else 
        {
            current_obj -> free_list_link = next_obj;
        }
        }
    return(result);
}

再從deallocate結束

  1. 釋放的內存大於128字節直接調用一級配置器進行釋放
  2. 將內存直接還給對應大小的鏈表就好了, 並不用直接釋放內存, 以便後面分配內存的時候快速.
static void deallocate(void *p, size_t n)
{
      obj *q = (obj *)p;
      obj * __VOLATILE * my_free_list;
    
      // 釋放的內存大於128字節直接調用一級配置器進行釋放
      if (n > (size_t) __MAX_BYTES) 
      {
        malloc_alloc::deallocate(p, n);
        return;
      }
      my_free_list = free_list + FREELIST_INDEX(n);
      q -> free_list_link = *my_free_list;
      *my_free_list = q;
}

統一的接口

定義符合STL規格的配置器接口, 無論是一級配置器仍是二級配置器都是使用這個接口進行分配的class

// 定義符合STL規格的配置器接口, 無論是一級配置器仍是二級配置器都是使用這個接口進行分配的
template<class T, class Alloc>
class simple_alloc {
  public:
    static T *allocate(size_t n)
    { return 0 == n? 0 : (T*) Alloc::allocate(n * sizeof (T)); }
    static T *allocate(void)
    { return (T*) Alloc::allocate(sizeof (T)); }
    static void deallocate(T *p, size_t n)
    { if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
    static void deallocate(T *p)
    { Alloc::deallocate(p, sizeof (T)); }
};

總結

用鏈表來保存不一樣字節大小的內存塊, 就很容易的進行維護, 並且每次的內存分配都直接能夠從鏈表或者內存池中得到, 提高了咱們申請內存的效率, 畢竟每次調用malloc和free效率是很低的, 特別是很小內存的時候.thread

STL默認的就是第二級配置器, 它會自動判斷咱們使用哪個配置器.效率

相關文章
相關標籤/搜索