1. 程式人生 > >SGI STL空間配置器-第一級空間配置器

SGI STL空間配置器-第一級空間配置器

一、SGI STL配置器簡介
如果要了解STL的實現,必須要了解空間配置器,因為整個STL的操作物件都放在容器之內,而容器一定需要配置空間以存放資料等資料。allocator叫做空間配置器而不是記憶體配置器,因為空間不一定是記憶體,也可以是磁碟或者其他輔助儲存介質。可以寫一個allocator直接向硬碟取空間。當然這裡介紹的allocator配置的是記憶體。
二、SGI標準的空間配置器
其實SGI也定義了一個符合部分標準,名為allocator的配置器,但是它自己不使用,也不建議我們使用,主要原因是效率不佳。它只是把C++的操作符::operator new和::operator delete做了一層簡單的封裝而已。下面是程式碼,可以看出空間配置器的標準介面,提供了預設的構造器、複製、析構等介面。另外這裡的SGI版本的allocator僅僅對底層的記憶體配置/釋放行為(::operator new和::operator delete)做了一層簡單的包裝,沒有效率上的強化。

#ifndef DEFALLOC_H  
#define DEFALLOC_H

#include <new.h>  
#include <stddef.h>  
#include <stdlib.h>  
#include <limits.h>  
#include <iostream.h>  
#include <algobase.h>  


template <class T>  
inline T* allocate(ptrdiff_t size, T*) {  
    set_new_handler(0
); T* tmp = (T*)(::operator new((size_t)(size * sizeof(T)))); if (tmp == 0) { cerr << "out of memory" << endl; exit(1); } return tmp; } template <class T> inline void deallocate(T* buffer) { ::operator delete(buffer); } template
<class T> class allocator { public: typedef T value_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; pointer allocate(size_type n) { return ::allocate((difference_type)n, (pointer)0); } void deallocate(pointer p) { ::deallocate(p); } pointer address(reference x) { return (pointer)&x; } const_pointer const_address(const_reference x) { return (const_pointer)&x; } size_type init_page_size() { return max(size_type(1), size_type(4096/sizeof(T))); } size_type max_size() const { return max(size_type(1), size_type(UINT_MAX/sizeof(T))); } }; class allocator<void> { public: typedef void* pointer; }; #endif

三、SGI特殊的空間配置器alloc
一般而言,我們所習慣的C++記憶體配置操作和釋放操作是這樣的:

 class Foo{...};
 Foo* pf = new Foo;  //配置記憶體,然後構造物件
 delete              //將物件析構,釋放記憶體

通常,C++中用new操作符來分配記憶體都包括兩個階段(如上):
(1)呼叫::operator new配置記憶體
(2)呼叫建構函式Foo::Foo()來構造物件內容

同理,delete操作也包括兩個階段:
(1)呼叫解構函式Foo::~Foo()將物件析構
(2)呼叫::operator delete釋放記憶體

為了精密分工,SGI allocator將兩個階段分開:
記憶體配置操作由alloc:allocate負責,記憶體釋放由alloc:deallocate負責;物件構造操作由::contructor()負責,物件析構由::destroy()負責。
配置器定義在標頭檔案中,它裡面又包括兩個檔案:

#include <stl_alloc.h>        // 負責記憶體空間的配置和器釋放  
#include <stl_construct.h>        // 負責物件的構造和析構  

下圖顯示了其結構:
圖一 標頭檔案 <memory>結構

1、物件的建構和結構函式construct()和destroy()
下圖顯示了這兩個函式的結構和功能。他們被包含在標頭檔案stl_construct.h中。
圖二 函式construct()和destroy()示意圖

函式construct()使用了定位new操作符,其原始碼:

template <class T1, class T2>  
inline void construct(T1* p, const T2& value) {  
  new (p) T1(value);    // 定為new操作符placement new; 在指標p所指處構造物件  
}

函式destroy則有兩個版本。
第一個版本較簡單,接受一個指標作為引數,直接呼叫物件的解構函式即可,其原始碼:

template <class T>  
inline void destroy(T* pointer) {  
    pointer->~T();   // 呼叫解構函式  
} 

第二個版本,其引數接受兩個迭代器,將兩個迭代器所指範圍內的所有物件析構掉。而且,它採用了trivial程式設計技法(這裡有介紹http://blog.csdn.net/mmshixing/article/details/51657168):依據元素的型別,判斷其是否有trivial destructor(無用的解構函式)進行不同的處理。這也是為了效率考慮。因為如果每個物件的解構函式都是trivial的,那麼呼叫這些毫無作用的解構函式會對效率造成影響。
下面看其原始碼:

//destroy()第二個版本,接受兩個迭代器。此函式設法找出元素的數值型別  
//然後利用__type_trivial<>求取最適當措施。  
Template <class ForwardInterator>  
Inline void destroy(ForwardInterator first,ForwardInterator last, T*)  
{  
    Typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;  
    __destroy_aux(first, last, trivial_destructor());  
}  

//下面是對__destroy_aux的過載,第三個引數分別為__true_type、__false_type  
//如果元素的數值型別(value type)有non-trivial 函式  
Template <class ForwardIterator>  
Inline void  
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {  
    for( ; first < last ; ++first)  
        destroy(&*first);  //對[first, last)範圍的多有物件析構掉!  

}  

//如果元素的數值型別(value type)有trivial 函式  
Template <class ForwardIterator>   
Inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}//什麼也不做  

2、空間的配置和釋放,std::alloc
物件構造前的空間分配和析構後的空間釋放,定義在標頭檔案

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)); }  
};  

SGI STL容器全部是使用這個simple_alloc介面。

第一級和第二級配置器之間的關係如下圖所示:
圖三 第一級配置器和第二級配置器
第一級和第二級配置器的包裝介面和運用方式如下:
這裡寫圖片描述

第一級配置器__malloc_alloc_template剖析
第一級配置器直接使用malloc(),free(),realloc()等C函式執行實際的記憶體配置、釋放、重配置操作,並實現出類似C++ new handler機制。它有獨特的out-of-memory記憶體處理機制:在丟擲std::bad_alloc異常之前,呼叫記憶體不足處理例程嘗試釋放空間,如果使用者沒有定義相應的記憶體不足處理例程,那麼還是會丟擲異常。詳細實現見函式oom_malloc(),oom_realloc()。
記憶體不足處理例程儲存在函式指標__malloc_alloc_oom_handler裡面。記憶體不足處理函式,由程式猿自己通過new-handler自己設計,在標頭檔案中這樣定義
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
這個在《effctive c++》第二版條款7中有詳細說明。

下面列出第一級配置器__malloc_alloc_template程式碼:

#if 0   
#   include <new>   
#   define  __THROW_BAD_ALLOC throw bad_alloc   
#elif !defined(__THROW_BAD_ALLOC)   
#   include <iostream.h>   
#   define  __THROW_BAD_ALLOC cerr << "out of memory" << endl; exit(1)   
#endif   

// malloc-based allocator. 通常比稍後介紹的 default alloc 速度慢,   
//一般而言是 thread-safe,並且對於空間的運用比較高效(efficient)。   
//以下是第一級配置器。   
//注意,無「template 型別引數」。至於「非型別引數」inst,完全沒派上用場。  
template <int inst>     
class __malloc_alloc_template {   

private:   
//以下都是函式指標,所代表的函式將用來處理記憶體不足的情況。   
// oom : out of memory.   
static void *oom_malloc(size_t);   
static void *oom_realloc(void *, size_t);   
static void (* __malloc_alloc_oom_handler)();   

public:   

static void * allocate(size_t n)   
{   
    void  *result =malloc(n);//第一級配置器直接使用 malloc()   
    // 以下,無法滿足需求時,改用 oom_malloc()   
    if (0 == result) result = oom_malloc(n);   
    return  result;   
}   
static void deallocate(void *p, size_t /* n */)   
{   
free(p); //第一級配置器直接使用 free()   
}   

static void * reallocate(void *p, size_t /* old_sz */, size_t new_sz)   
{   
    void  *  result  =realloc(p, new_sz);//第一級配置器直接使用 rea  
    // 以下,無法滿足需求時,改用 oom_realloc()   
    if (0 == result) result = oom_realloc(p, new_sz);   
    return  result;   
}   

//以下模擬 C++的 set_new_handler(). 換句話說,你可以透過它,   
//指定你自己的 out-of-memory handler   
static void (* set_malloc_handler(void (*f)()))()   
{   
    void  (*  old)()  =  __malloc_alloc_oom_handler;   
__malloc_alloc_oom_handler = f;   
    return(old);   
}   
};   

// malloc_alloc out-of-memory handling   
//初值為 0。有待客端設定。   
template <int inst>   
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;   

template <int inst>   
void * __malloc_alloc_template<inst>::oom_malloc(size_t n)   
{   
    void  (* my_malloc_handler)();   
    void  *result;   

    for (;;)  {   

//不斷嘗試釋放、配置、再釋放、再配置…   
my_malloc_handler = __malloc_alloc_oom_handler;   
        if  (0  ==  my_malloc_handler)  {  __THROW_BAD_ALLOC; }   
        (*my_malloc_handler)();//呼叫處理例程,企圖釋放記憶體。   
        result = malloc(n);  //再次嘗試配置記憶體。   
        if  (result)  return(result);   
    }   
}   

template <int inst>   
void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)   
{   
    void  (* my_malloc_handler)();   
    void  *result;   
       for (;;)  {  //不斷嘗試釋放、配置、再釋放、再配置…   
my_malloc_handler = __malloc_alloc_oom_handler;   
        if  (0  ==  my_malloc_handler)  {  __THROW_BAD_ALLOC; }   
        (*my_malloc_handler)();//呼叫處理例程,企圖釋放記憶體。   
        result = realloc(p, n);//再次嘗試配置記憶體。   
        if  (result)  return(result);   
    }   
}   

//注意,以下直接將引數 inst指定為 0。   
typedef __malloc_alloc_template<0> malloc_alloc;   

以上為STL空間配置器及第一級配置器__malloc_alloc_template詳細內容,大部分是《STL原始碼剖析》這本書上的,自己捋一下,思路更加清晰。