STL空間配置器(二)

 ***上一篇是對STL空間配置器的入門級理解,在這一篇中,我將討論更加深入的SGI STL空間適配器的內容。在下一節中,我將根據自己的理解,結合STL標準接口,實現一個符合STL標準的具有次級配置能力的簡單空間配置器,將剪掉一切不需要的代碼,在加上我自己的理解,實現一個更容易閱讀與理解的空間配置器。
 在開始進入正題之前,我先來談談爲什麼要花這麼長的時間在看空間配置器的部分,而且對於學習如何使用STL來說,根本就不需要知道有空間適配器的存在,因爲我們在實際編程中是不需要與空間配置器打交道的。那我想說,如果僅僅是爲了學習如何使用STL去編程,那大可不必去閱讀他的源代碼,只需要記住每個函數的功能和用法,然後就可以解決問題。但那不是學會STL,那是學會用STL。這是最爲容易做到的事情,我相信只要有點編程語言基礎的人,給上一個小時就完全可以用STL的各種組建來做事情了。***

但是這遠遠不夠,學習STL的核心在於理解他的設計原理,明白了原理,才能更好的去用STL,比如,如果你不知道STL容器之間的區別,那我相信大多數人在哪都會用一個容器vector,這個容器已然成了“通用容器”,確實,對於一般性問題,vector足夠強大去承擔各種場景,但是,你想想,如果只需要vector就能把事情做好,那爲什麼STL還有list呢?爲什麼還有queue呢?那map呢?思考從這裏開始,但是對於本節來講,這些思考到這裏結束。
我們現在來討論SGI STL的空間配置器,這是STL能正常、合理、高效工作的基礎和動力,他讓使用STL的各個組件變得更加容易,有了空間配置器,我們就不需要自己去管理內存,當然也不需要自己去處理內存不足的問題。既然是STL的基礎,那麼就要打好這個基礎,STL從這裏開始,我想,最後還是會回到這裏。
STL空間配置器有一套標準接口,這對我們很重要,因爲如果我們想要自己寫一套屬於自己的STL版本,那就應該努力符合STL標準。SGI當然也有一個符合STL標準的空間配置器,但是在源代碼的開始,SGI就奉勸大家不要輕易使用這個版本的空間配置器,留着它只是爲了兼容STL標準,所以,很明顯這個文件不會被SGI STL用到,但是我們還是來看看它吧,也乘機瞭解瞭解STL標準的空間配置器張什麼樣子。

//爲了兼容STL標準而寫的這個文件,實際上SGI STL並沒有使用這個文件作爲他的空間配置器
//下面這個類提供標準STL申請對象函數allocate
template <class T>
inline T* allocate(ptrdiff_t size, T*) {
    set_new_handler(0); //應該還記得new-handler機制吧(out-of-memory)
    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);
}
//標準SGI框架配置器
template <class T>
class allocator {
public:
    //一組別名定義,不明真相的我以爲這很沒有必要,可以直接將T改名爲Type就ok啦
    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;
    //配置n個空的對象
    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))); 
    }
};
//ok,在你的需要配置的地方,使用這個就可以了
class allocator<void> {
public:
    typedef void* pointer;
};

看了上面的代碼,我們應該知道了標準STL空間配置器應該含有哪些接口了吧?我將在下一節中實現一個標準空間配置器。
SGI真正使用的配置器的設計非常巧妙,它實現了一級配置器和次級配置器,兩級配置器相互配合,高效的爲STL各種組建提供動力。我們已經在上一節中介紹了兩級配置器的原理,還介紹了它們是如何協調工作的,所以在這裏我也不會再次花費篇幅來講這兩級配置器,但是,我想講講內存管理。
當我們聽到內存管理的時候,一般會想到操作系統,內核,MMU這些東西,確實,在我們的應用中,特別是在C++語言中,我們自己管理內存較爲簡單,new和delete配合使用即可,但即便如此,也免不了一些內存泄漏的BUG,作爲一個高效安全的c++標準庫,STL用空間適配器將STL的組建的內存管理得井然有序,不需要我們自己花費精力去管理。那什麼是內存管理呢?本質是什麼呢?我以爲,內存管理其實就是管理地址空間,內存的申請與釋放相當於對某一地址空間的上標記與解標記,上標記則表明已經被分配,用不用是程序做的事情,反正這段地址上了標記就是表明不能被再次分配了。有人可能會說我不是在說廢話嗎?內存不就是地址嗎?其實,我必不反對將內存說成地址空間,因爲我就是那麼去理解內存的,進一步,對於32位的地址線,最大內存是2^32=4Gb,所以從內存是怎麼得到的來看,確實,內存就是地址空間,能提供的地址空間越大,那能管理的內存就越多,64位的地址線能管理的內存肯定大於32位的。
但是,內存不是地址,我只能說這麼多!
對於STL裏面的空間適配器,它使用了malloc和free函數來實現內存的分配與回收,沒有使用我們熟悉的c++裏面的new和delete,很明顯,malloc和free的效率高於new和delete,但有可能對於程序員來說,new和delete更爲熟悉和安全。
SGI STL空間適配器用到了一種稱爲“內存池”的技術,這是一種較爲高級的內存管理技術,在實現上,可以首先向操作系統申請一塊較大的內存空間,然後以後的每次內存操作都使用內存池接口,這樣就可以實現用內存池來管理程序中的內存操作,如果設計合理,使用內存池可以提高代碼的效率,還能有效減少內存碎片化問題,比直接使用malloc和free好很多。
在下一節中,我將實現一個通用的內存池,在這之前,還是先看看SGI空間配置器的內存池是如何設計的吧。(在我自己實現的空間配置器裏面,我將使用內存池技術,所以到時候再一次詳談)。
最後,我們來看看SGI STL的空間配置器的文件組織模式。
下面是在文件memory裏面發現的頭文件:

#include <stl_algobase.h>
#include <stl_alloc.h>   //負責內存空間的配置與釋放,裏面有一二級配置器
#include <stl_construct.h> //負責對象內容的構造與析構
#include <stl_tempbuf.h>
#include <stl_uninitialized.h>   //全局函數,輔助
#include <stl_raw_storage_iter.h>

所以,我們主要關注三個文件,一個是stl_alloc.h,這是SGI使用的空間配置器的文件,stl_construst.h文件裏面定義了construct和destory函數用來操作對象,stl_uninitialized.h文件則包含兩個主要的全局內存操作函數,複製和填充內存。
因爲目前還在配置器階段,所以我們將使用SGI的這些函數來爲我們工作,等時機成熟,我們將改寫這些函數。
好啦,下面我們就要開始真正的寫一個自己的配置器了,主要內容依然參照SGI STL的空間配置器,但是去掉了一切高級的功能(比如線程支持),這些支持將在最後加上。

/*
 * CopyRight (c) 2016
 * HuJian in nankai edu.
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.  Silicon Graphics makes no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied warranty.
 *
 * Time :2016/4/5 in nankai edu
*/
#ifndef _HJ_STL_ALLOC_H_
#define _HJ_STL_ALLOC_H_
#include<cstdio>
#include<climits>
#include<iostream>
#include<cstddef>

//if out of memory,just exit after print the info.
#define  _THROW_BAD_ALLOC cerr<<"out of memory"<<endl; exit(1);

//this is the new-handler handler,using when oom
void(*_malloc_alloc_oom_handler_hjstl)() = 0;

//this is the first class of mmu in hjstl,this class will work
//if the second dispatch the job to it.
template<int inst>
class _malloc_alloc_first{
private:
    static void* oom_malloc(size_t); //this function use to malloc and has the new-handler
    static void* oom_remalloc(void*, size_t);//re-malloc
public:
    //this function is the first allocator of hjstl
    static void* allocate(size_t size)
    {
        void* result = malloc(size);
        //check and dispatch it to oom_malloc if oom 
        if (0 == result) result = oom_malloc(size);
        return result;
    }
    //re-malloc,same as malloc
    static void* remallocate(void* old, size_t/*old mem size*/, size_t new_size)
    {
        void* result = realloc(old, new_size);
        if (0 == result) result = oom_remalloc(old, new_size);
        return result;
    }
    //de-allocate
    static void deallocate(void* mem, size_t size)
    {
        //we not use the memory pool,so just use free
        //but we will use the memory pool to manage the memory of 
        //hjstl later
        free(mem);
    }

    //we can use this function to set the new handler
    static void(*set_malloc_handler(void(*handler)()))()
    {
        //get the old oom handler,and we will return it to process
        void(*old)() = _malloc_alloc_oom_handler_hjstl;
        //set the new handler
        _malloc_alloc_oom_handler_hjstl = handler;
        return (old);
    }
};//end of malloc alloc first 


///---impelment the first allocate of hjstl
template<int inst>
void* _malloc_alloc_first<inst>::oom_malloc(size_t size)
{
    //this is the oom handler,this function will use this function when 
    //out of memory
    void(*my_malloc_handler)();
    void* result;
    for (;;){//i will loop to test till i get the memory
        my_malloc_handler = _malloc_alloc_oom_handler_hjstl;
        if (0 == my_malloc_handler) { _THROW_BAD_ALLOC; }
        //else,the handler defined,i can use it,run this handler
        (*my_malloc_handler)();
        result = malloc(size);
        if (result) return result;//succeed!!!
    }
}
//same as malloc
template<int inst>
void* _malloc_alloc_first<inst>::oom_remalloc(void* old, size_t size)
{
    void (* my_handler)();
    void* result;
    for (;;){
        my_handler = _malloc_alloc_oom_handler_hjstl;
        if (0 == myhandler) { _THROW_BAD_ALLOC; }
        (*my_handler)();
        result = realloc(old, size);
        if (result) return result;
    }
}

//ok,i need to define a first allocate,and if the second 
//allocate can not do it,let me try!
typedef _malloc_alloc_first<0> malloc_alloc_first_hjstl;

//we do this,because we need to let the HJSTL standard with STL
template<class Type,class HJALLOC>
class STD_STL_Alloc{
public:
    static Type* allocate(size_t size){
        return 0 == n ? 0 : (Type*)HJALLOC::allocate(size*sizeof(Type));
    }
    static Type* allocate(){
        return (Type*)HJALLOC::allocate(sizeof(Type));
    }
    static void deallocate(Type* mem,size_t size){
        if (size != 0){
            HJALLOC::deallocate(mem,size*sizeof(Type));
        }
    }
    static void deallocate(Type* mem){
        HJALLOC::deallocator(mem,sizeof(mem));
    }
};

//ok,start to write the second allocator
//and some auxiliary variable neeed to define here
#define  _ALIGN       8                     //the min mem block
#define  _MAX_BYTES   128               //the max mem block
#define  _NFREELISTS  _MAX_BYTES/_ALIGN  //this is the num of free list

template<int inst>//no thread supposed
class _malloc_alloc_second{
private:
    //round up a size
    static size_t HJSTL_ROUND_UP(size_t bytes){
        //ok,i use the SGI STL's round up method,it nice 
        return (((bytes)+_ALIGN - 1) & ~(_ALIGN - 1));
    }
    //this function will find the free list's position in the array actually
    static size_t HJSTL_FREELIST_INDEX(size_t bytes){
        //i just copy the sgi stl's code here.
        return (((bytes)+_ALIGN - 1) / _ALIGN - 1);
    }
    //this is the free list's node strcture
    //this is a usion.just copy the sgi stl's 
    union free_list_node{
        free_list_node* free_list_link;//store the next free list
        char            data[1];//if assigned,this is the user's data
    };
    //this is the array of free lists
    static free_list_node* free_list[_NFREELISTS];

    //return an object of size n,and this function will re-fill the
    //free list,update the free list array .
    static void* refill(size_t size);

    //memory pool,and allocate a chunk for nobjs of size 'size'
    //if the memory of pool is not so enough,this function will 
    //return the real num of nobjs
    static char* chunk_alloc(size_t size, int &nobjs);

    //this is a sample memory pool
    static char* pool_free_start;
    static char* pool_free_end;
    static size_t  heap_size;

    //the second allocate
    static void* allocate(size_t size)
    {
        free_list_node*  my_free_list;
        free_list_node*  result;
        //dispatch the job,if the ask memory bigger _MAX_BYTES,use
        //the first allocate,else use the second allocate
        if (size > (size_t)_MAX_BYTES){
            return (malloc_alloc_first_hjstl::allocate(size));
        }
        //else ,use the second allocate to do this job
        //find the aim-free list 
        my_free_list = free_list + HJSTL_FREELIST_INDEX(size);
        //now,the result is the header pointer of this list
        result = *my_free_list;
        if (0 == result){//shirt,this list is empty,i will call refill to help me
            void* r = refill(HJSTL_ROUND_UP(size));
            return r;
        }
        //else,this free list is not empty,just use the header pointer
        //and change the free list.
        *my_free_list = result->free_list_link;
        return result;
    }

    //the second deallocate
    static void deallocate(void* mem, size_t size)
    {
        free_list_node* my_free_list;
        free_list_node* new_header_pointer = (free_list_node*)mem;
        //dispatch the job
        if (size > (size_t)_MAX_BYTES){
            malloc_alloc_first_hjstl::deallocate(mem, size);
            return;
        }
        //else,solve it.
        //first of all,i will find the free list's position
        my_free_list = free_list + HJSTL_FREELIST_INDEX(size);
        //change the list
        new_header_pointer->free_list_link = my_free_list;
        *my_free_list = new_header_pointer;
        return;
    }

    //this is the second reallocate
    static void* reallocate(void* old, size_t old_size, size_t new_size);

};//end of second allocator

//heihei,this is the default alloc,we will use this alloc in our project
typedef _malloc_alloc_second<0> hjstl_alloc;
#endif  //end of _HJ_STL_ALLOC_H_

上面是今天寫的代碼,很多內容複製了SGI的寫法,因爲過於經典,在效率面前就不露三腳貓功夫了,下一節將接着實現refill函數和內存池函數,最後所有的內存操作除了不能使用之外都會使用我自己寫的一個內存池管理單元,我將單獨學習與記錄內存池部分。

——今天好傻,壓力很大,大三不好玩—
胡建,南開,2016

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章