C++STL中Allocator分析

C++STL中Allocator分析

C++ SGI STL的allocator分为两级:

  • 第一级配置器:超过128bytes的内存分配通过第一级分配器进行分配。

  • 第二级配置器:不超过128bytes的内存分配通过第二级分配器进行分配。

为了简单起见,文本在讨论的时候无考虑多线程的场景以及模板化。

第一级配置器

第一级配置器的实现比较简单,直接通过malloc请求内存,通过free释放内存。若通过malloc请求内存失败,则先尝试通过调用用户注册的处理函数尝试释放不再使用的内存,之后再次使用malloc进行请求内存。

具体的代码如下:

class __malloc_alloc{
private:
    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);
    static void deallocate(void *p, size_t n);
    static void *reallocate(void *p, size_t old_sz, size_t new_sz);
    static void (* set_malloc_handler(void (*f)()))(){
        void (*old)() = __malloc_alloc_oom_handler;
        __malloc_alloc_oom_handler = f;
        return old;
    }

};

void (*__malloc_alloc::__malloc_alloc_oom_handler)() = 0;

void *__malloc_alloc::oom_malloc(size_t n){
    void (*my_malloc_handler)();
    void *result;
    for(;;){
        my_malloc_handler = __malloc_alloc_oom_handler;             
        if(0 == my_malloc_handler) {
            std::cerr<<"out of memory"<<std::endl;      //若没有注册回调函数,抛出异常
            exit(0);
        }
        (*my_malloc_handler)();     //回调用户注册的内存释放函数,以寻求释放空闲内存
        result = malloc(n);
        if(result) return result;
    }
}

void *__malloc_alloc::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) {
            std::cerr<<"out of memory"<<std::endl;
            exit(0);
        }
        (*my_malloc_handler)();
        result = realloc(p, n);
        if(result) return result;
    }
}

void *__malloc_alloc::allocate(size_t n){
    void *result = malloc(n);                   //第一级适配器直接使用malloc请求内存
    if(0 == result) result = oom_malloc(n);     //若malloc无法请求内存,则先调用用户注册的回调函数尝试释放内存
    return result;
}

void __malloc_alloc::deallocate(void *p, size_t n){
    free(p);                                    //第一级适配器直接使用free释放内存
}

void *__malloc_alloc::reallocate(void *p, size_t old_sz, size_t new_sz){
    void *result = realloc(p, new_sz);
    if(0 == result) result = oom_realloc(p, new_sz);
    return result;
}
第二级配置器

第二级配置多了一些机制,避免太多小额区块造成内存的碎片。小额区块带来的起始不仅是内存碎片,配置时的额外负担也是一个大问题。SGI第二级配置器的做法是,如果区块够大,超过128b时,就移交第一级配置器处理。当区块小于128b时,则以内存池管理,此法又称为次层配置:每次配置一大块内存,并维护对应之自由链表。下次若再有相同大小的内存需求,就直接从free_list中拿出。如果客户端还释还小额区块,就由配置器回收到free_list中。为了方便管理,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数(例如客户端需求为30b,就自动调整为32b)。

其维护两个数据结构:

空闲链表:16个free_lists,各种管理大小为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128btyes的小额区块。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内存池:在堆上分配的内存空间,free_start为可用空间起点,free_end为可用空间终点。

enum {__ALIGN = 8};             //小型区块的上调边界
enum {__MAX_BYTES = 128};       //小型区块的上限
enum {__NFREELISTS = __MAX_BYTES/__ALIGN};  //free-lists个数

class __default_alloc{
private:
    static size_t ROUND_UP(size_t bytes){       //将bytes上调至8的倍数
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
    }
    union obj{
        union obj * free_list_link;
        char client_data[1];
    };
    static obj * volatile free_list[__NFREELISTS];
    static size_t FREELIST_INDEX(size_t bytes){
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
    }
    static void *refill(size_t n);          //填充大小n的空闲链表
    static char *chunk_alloc(size_t size, int &nobjs);  //从内存池中分配size*nobjs大小的内存空间

    static char *start_free;    //内存池开始地址
    static char *end_free;      //内存池结束地址
    static size_t heap_size;

public:
    static void * allocate(size_t n);
    static void deallocate(void *p, size_t n);
    static void * reallocate(void *p, size_t old_sz, size_t new_sz);
};

void * __default_alloc::allocate(size_t n){
    obj * volatile *my_free_list;
    obj * result;
    if(n > (size_t)__MAX_BYTES){    //大于阈值则通过第一级分配器分配
        return __malloc_alloc::allocate(n);
    }

    my_free_list = free_list + FREELIST_INDEX(n);   //寻找合适的空闲链表
    result = *my_free_list;
    if(result==0){         //空闲链表中无空闲空间
        void *r = refill(ROUND_UP(n));  //填充空闲链表
        return r;
    }
    *my_free_list = result->free_list_link;
    return result;
}

void  __default_alloc::deallocate(void *p, size_t n){
    obj *q = (obj*) p;
    obj * volatile * my_free_list;
    if(n > (size_t)__MAX_BYTES){    //大于阈值则通过第一级分配器分配
        __malloc_alloc::deallocate(p, n);
        return;
    }
    my_free_list = free_list + FREELIST_INDEX(n);   //寻找对应的空闲链表
    q->free_list_link = *my_free_list;      //将内存区域插入空闲链表
    *my_free_list = q;
    return;
}

void *__default_alloc::refill(size_t n){
    int nobjs = 20;
    char *chunk = chunk_alloc(n, nobjs);    //尝试请求n*nobjs空间,实际得到的数量为nobjs
    obj *volatile * my_free_list;
    obj *result;
    obj* current_obj, *next_obj;
    int i;

    //如果只获得一个区块,这个区块就分配给调用者使用,free_list无新结点
    if(1==nobjs) return chunk;
    my_free_list = free_list + FREELIST_INDEX(n);

    result = (obj *)chunk;
    *my_free_list = next_obj = (obj *)(chunk + n);
    for(i=1;;i++){      //构造空闲链表
        current_obj = next_obj;
        next_obj = (obj*)((char*)next_obj + n);
        if(nobjs-1==i){
            current_obj->free_list_link = 0;
            break;
        }
        else{
            current_obj->free_list_link = next_obj;
        }
    }   
    return result; 
}

char * __default_alloc::chunk_alloc(size_t size, int &nobjs){
    char * result;
    size_t total_bytes = size*nobjs;
    size_t bytes_left = end_free - start_free;

    if(bytes_left >= total_bytes){ //内存池剩余空间完全满足需求量
        result = start_free;
        start_free += total_bytes;
        return result;
    }
    else if(bytes_left>=size){      //内存池剩余空间不能完全满足需求量,但足够供应一个以上的区块
        nobjs = bytes_left/size;
        total_bytes = size*nobjs;
        result = start_free;
        start_free += total_bytes;
        return result;
    }
    else{   //内存池剩余空间连一个区块的大小都无法提供
        size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size>>4);
        if(bytes_left>0){ //内存池中还有一些零头,先配给适当的free_list;
            obj * volatile *my_free_list = free_list + FREELIST_INDEX(bytes_left);
            ((obj *)start_free) -> free_list_link = *my_free_list;
            *my_free_list = (obj *)start_free;
        }
        start_free = (char*)malloc(bytes_to_get); //配置heap空间,用来补充内存池
        if(0==start_free){  //heap空间不足,malloc失败
            int i;
            obj *volatile *my_free_list, *p;
            //检查当前free_list,看是否有比size大的空闲的空间,并对其进行重新配置
            for(i = size; i <= __MAX_BYTES; i += __ALIGN){
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if(0 != p){         //在free_list找到合适的区域
                    *my_free_list = p->free_list_link;
                    start_free = (char *)p;
                    end_free = start_free + i;
                    return chunk_alloc(size, nobjs); //递归调用自己,修正nobjs
                    //任何残余的零头总将被编入适当的free_list中备用
                }
            }
            end_free = 0; //执行到这一步说明山穷水尽了
            //调用第一级配置器,看看out-of-memory机制能否尽点力
            start_free = (char *)__malloc_alloc::allocate(bytes_to_get);
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        return chunk_alloc(size, nobjs); //递归调用自己,为了修正nobjs
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Content Containers...................................................................................................................1 Item 1. Choose your containers with care...........................................................1 Item 2. Beware the illusion of container-independent code................................4 Item 3. Make copying cheap and correct for objects in containers.....................9 Item 4. Call empty instead of checking size() against zero..............................11 Item 5. Prefer range member functions to their single-element counterparts...12 Item 6. Be alert for C++'s most vexing parse...................................................20 Item 7. When using containers of newed pointers, remember to delete the pointers before the container is destroyed............................................................22 Item 8. Never create containers of auto_ptrs....................................................27 Item 9. Choose carefully among erasing options..............................................29 Item 10. Be aware of allocator conventions and restrictions..........................34 Item 11. Understand the legitimate uses of custom allocators........................40 Item 12. Have realistic expectations about the thread safety of STL containers. 43 容器 条款1: 仔细选择你要的容器 条款2: 小心对“容器无关代码”的幻想 条款3: 使容器里对象的拷贝操作轻量而正确 条款4: 用empty来代替检查size是否为0 条款5: 尽量使用范围成员函数代替他们的单元素兄弟 条款6: 警惕C++的及其令人恼怒的分析 条款7: 当使用new得指针的容器时,切记在容器销毁前delete那些指针 条款8: 千万不要把auto_ptr放入容器 条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法 条款12: 对STL容器的线程安全性的期待现实一些

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值