STL源碼剖析——空間的配置與釋放

    C++的內存配置基本操作是  ::operator new(),內存釋放的基本操作是 ::operator delete()。這兩個全局函數相當於C的malloc()和free()函數。是的,正是如此,STL正是以malloc()和free()完成內存的配置與釋放。

    但是考慮到小型區塊所可能造成的內存破碎問題,STL中設計了雙層級配置器, 第一級配置器直接使用malloc()和free(),第二級配置器則視情況採用不用的策略:當配置區塊超過128bytes時,視之爲“足夠大”,便調用第一級配置器;當配置區塊小於128bytes時,視之爲“過小”,爲了降低額外負擔,便採用複雜的memory pool整理方式(後面會介紹),而不再求助於第一級配置器。

    接下來,我們來詳解這兩級空間配置器

    第一級配置器剖析

    第一級配置器以malloc(),free(),realloc()等C函數執行實際的內存配置、釋放、重配置操作,並實現出類似C++ new-handler的機制。所謂的new-handler機制是,你可以要求系統在內存配置無法被滿足時,調用一個你所指定的函數。下面我將展示一個示例: 

// new_handler example
#include <iostream>     // std::cout
#include <cstdlib>      // std::exit
#include <new>          // std::set_new_handler

void no_memory () {
  std::cout << "Failed to allocate memory!\n";
  std::exit (1);
}

int main () {
  std::set_new_handler(no_memory);
  std::cout << "Attempting to allocate 1 GiB...";
  char* p = new char [1024*1024*1024];
  std::cout << "Ok\n";
  delete[] p;
  return 0;
}

    請注意,STL第一級配置器在調用malloc()和realloc()不成功後,會嘗試不斷調用“內存不足處理例程”,期望在某次調用之後,獲得足夠的內存而圓滿完成任務,但如果“內存不足例程”未被客戶端設定,便會拋出bad_alloc異常信息,或利用exit(1)中止程序。

    第二級配置器剖析

    STL第二級配置器的做法是,如果區塊足夠大,超過128bytes時,就移交給第一級配置器處理。當區塊小於128bytes時,則以內存池(memory pool)管理,此法又稱爲次級配置:每次配置一塊大內存,並維護對應之自由鏈表(free-list)。下次若再有相同大小的內存需求,就直接從free-list中取出。如果客端釋放小額區塊時,就由配置器回收到free-lists中——是的,別忘了,配置器除了負責配置,也負責回收。爲了方便管理,STL第二級配置器會主動將任何小額區塊的內存需求上調至8的倍數(例如客端要求30bytes,就自動調整爲32bytes),並維護16個free-lists,各自管理大小分別爲8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小額區塊。

    瞭解了free-list之後我們就會產生一個疑問:如果當分配內存的時候發現free list中沒有可用的區塊了時,會怎麼辦呢?

    解決方法就是:當發現free list中沒有可用區塊了時,就會調用一個名爲refill()的函數準備爲free list重新填充空間。新的空間將調用malloc()取自內存池,缺省取得20個新節點(新區快),但萬一內存池空間不足,獲得的節點數(區塊數)可能小於20。

    萬一山窮水盡,整個堆空間都不夠了,malloc()行動失敗,就四處尋找有無“尚未使用區塊,且區塊足夠大”之free lists,找到了就挖出一塊交出,找不到就調用第一級配置器。第一級配置器其實也是使用malloc()來配置內存,但它有out-of-memory機制(類似於new-handler機制),或許有機會釋放其他的內存拿來此處使用。如果可以就陳宮,否則拋出bad_alloc異常。

發佈了58 篇原創文章 · 獲贊 1169 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章