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容器巧妙的避開了繁瑣且易出錯的內存管理工作。

隱藏在這些容器后的內存管理工作是通過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的倍數)從對應的空閑塊列表取表頭塊給用戶。

這種做法有兩個優點:

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM