STL內存管理器的分配策略

STL提供了不少泛型容器,如vector,list和map。程序員在使用這些容器時只需關心什麼時候往容器內塞對象,而不用關心如何管理內存,須要用多少內存,這些STL容器極大地方便了C++程序的編寫。例如能夠經過如下語句建立一個vector,它其實是一個按需增加的動態數組,其每一個元素的類型爲int整型:程序員

stl::vector<int> array;算法

擁有這樣一個動態數組後,用戶只須要調用push_back方法往裏面添加對象,而不須要考慮須要多少內存:數組

array.push_back(10); 
array.push_back(2);操作系統

vector會根據須要自動增加內存,在array退出其做用域時也會自動銷燬佔有的內存,這些對於用戶來講是透明的,stl容器巧妙的避開了繁瑣且易出錯的內存管理工做。.net

隱藏在這些容器後的內存管理工做是經過STL提供的一個默認的allocator實現的。固然,用戶也能夠定製本身的allocator,只要實現allocator模板所定義的接口方法便可,而後經過將自定義的allocator做爲模板參數傳遞給STL容器,建立一個使用自定義allocator的STL容器對象,如:指針

stl::vector<int, UserDefinedAllocator> array;對象

大多數狀況下,STL默認的allocator就已經足夠了。這個allocator是一個由兩級分配器構成的內存管理器,當申請的內存大小大於128byte時,就啓動第一級分配器經過malloc直接向系統的堆空間分配,若是申請的內存大小小於128byte時,就啓動第二級分配器,從一個預先分配好的內存池中取一塊內存交付給用戶,這個內存池由16個不一樣大小(8的倍數,8~128byte)的空閒列表組成,allocator會根據申請內存的大小(將這個大小round up成8的倍數)從對應的空閒塊列表取表頭塊給用戶。blog

這種作法有兩個優勢:接口

1)小對象的快速分配。小對象是從內存池分配的,這個內存池是系統調用一次malloc分配一塊足夠大的區域給程序備用,當內存池耗盡時再向系統申請一塊新的區域,整個過程相似於批發和零售,起先是由allocator向總經商批發必定量的貨物,而後零售給用戶,與每次都總經商要一個貨物再零售給用戶的過程相比,顯然是快捷了。固然,這裏的一個問題時,內存池會帶來一些內存的浪費,好比當只需分配一個小對象時,爲了這個小對象可能要申請一大塊的內存池,但這個浪費仍是值得的,何況這種狀況在實際應用中也並很少見。ip

2)避免了內存碎片的生成。程序中的小對象的分配極易形成內存碎片,給操做系統的內存管理帶來了很大壓力,系統中碎片的增多不但會影響內存分配的速度,並且會極大地下降內存的利用率。之內存池組織小對象的內存,從系統的角度看,只是一大塊內存池,看不到小對象內存的分配和釋放。

實現時,allocator須要維護一個存儲16個空閒塊列表表頭的數組free_list,數組元素i是一個指向塊大小爲8*(i+1)字節的空閒塊列表的表頭,一個指向內存池起始地址的指針start_free和一個指向結束地址的指針end_free。空閒塊列表節點的結構以下:

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

這個結構能夠看作是從一個內存塊中摳出4個字節大小來,當這個內存塊空閒時,它存儲了下個空閒塊,當這個內存塊交付給用戶時,它存儲的時用戶的數據。所以,allocator中的空閒塊鏈表能夠表示成:obj* free_list[16];

allocator分配內存的算法以下:

算法:allocate 
輸入:申請內存的大小size 
輸出:若分配成功,則返回一個內存的地址,不然返回NULL 

    if(size大於128){ 啓動第一級分配器直接調用malloc分配所需的內存並返回內存地址; 
    else { 
        將size向上round up成8的倍數並根據大小從free_list中取對應的表頭free_list_head; 
        if(free_list_head不爲空){ 
            從該列表中取下第一個空閒塊並調整free_list; 
            返回free_list_head; 
        } 
     } else { 
        調用refill算法創建空閒塊列表並返回所需的內存地址; 
    } 
}

從新填充算法:

算法: refill 
輸入:內存塊的大小size 
輸出:創建空閒塊鏈表並返回第一個可用的內存塊地址 

    調用chunk_alloc算法分配若干個大小爲size的連續內存區域並返回起始地址chunk和成功分配的塊數nobj; 
    if(塊數爲1)直接返回chunk; 
    else{ 
        開始在chunk地址塊中創建free_list; 
        根據size取free_list中對應的表頭元素free_list_head; 
        將free_list_head指向chunk中偏移起始地址爲size的地址處, 即free_list_head=(obj*)(chunk+size); 
        再將整個chunk中剩下的nobj-1個內存塊串聯起來構成一個空閒列表; 
        返回chunk,即chunk中第一塊空閒的內存塊; 
      } 
   }

塊分配算法

算法:chunk_alloc 
輸入:內存塊的大小size,預分配的內存塊塊數nobj(以引用傳遞) 
輸出:一塊連續的內存區域的地址和該區域內能夠容納的內存塊的塊數 

    計算總共所需的內存大小total_bytes; 
    if(內存池中足以分配,即end_free - start_free >= total_bytes) { 
        則更新start_free; 
        返回舊的start_free; 
    }  
    else if(內存池中不夠分配nobj個內存塊,但至少能夠分配一個){ 
        計算能夠分配的內存塊數並修改nobj; 
        更新start_free並返回原來的start_free; 
     } 
     else { //內存池連一塊內存塊都分配不了 
         先將內存池的內存塊鏈入到對應的free_list中後; 
         調用malloc操做從新分配內存池,大小爲2倍的total_bytes加附加量,start_free指向返回的內存地址; 
         if(分配不成功) { 
              if(16個空閒列表中尚有空閒塊) 
                   嘗試將16個空閒列表中空閒塊回收到內存池中再調用chunk_alloc(size, nobj); 
              else  
                   調用第一級分配器嘗試out of memory機制是否還有用; 
          } 
      } 
      更新end_free爲start_free+total_bytes,heap_size爲2倍的total_bytes; 
      調用chunk_alloc(size,nobj); 
}

假設這樣一個場景,free_list[2]已經指向了大小爲24字節的空閒塊鏈表,如圖1所示,當用戶向allocator申請21字節大小的內存塊時,allocaotr會首先檢查free_list[2]並將free_list[2]所指的內存塊分配給用戶,而後將表頭指向下一個可用的空閒塊,如圖2所示。注意,當內存塊在鏈表上是,前4個字節是用做指向下一個空閒塊,當分配給用戶時,它是一塊普通的內存區。

clip_image001

圖1   某時刻allocator的狀態

clip_image002

圖2 分配24字節大小的內存塊

http://blog.csdn.net/adcxf/article/details/6437880

相關文章
相關標籤/搜索