STL_Allocator内存配置器

STL_Allocator内存配置器

STL的内存配置器考虑到了小型的区块可能造成内存破碎问题,SGI STL 设计了双层级配置器,第一层配置器直接使用malloc() 和 free().第二层配置器则视情况采用不同的策略:但配置区块超过 128 bytes时,调用第一级配置器。当配置区块小于 128 bytes时,采用复杂的 memory pool 方式。

1. 第一级配置器

_ _malloc_alloc_template:

class __malloc_alloc_template {
	private:
	static void * allocate(size_t n)
	{
	    void *result = malloc(n);                    //第一级配置器,直接使用malloc()
	    //如果内存不足,则调用内存不足处理函数oom_alloc()来申请内存
	    if (0 == result) result = oom_malloc(n);
	    return result;
	}

	static void deallocate(void *p, size_t /* n */)
	{
	    free(p);            //第一级配置器直接使用 free()
	}
    template <int inst>
	void * __malloc_alloc_template<inst>::oom_malloc(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; }    //如果自己没有定义oom处理函数,则编译器毫不客气的抛出异常。
	        (*my_malloc_handler)();                                //执行自定义的oom处理函数
	        result = malloc(n);                                //重新分配空间
	        if (result) return(result);                            //如果分配到了,返回指向内存的	指针
	    }
	}
}

通过代码可以看出在第一级配置器中是用了原生的配置器malloc来分配内存,free来释放内存,当内存不足时去调用oom_malloc函数去申请内存,具体的申请函数的方法需要自己去定义。

2. 第二级配置器

_ _default_alloc_template:

  1. 维护了16个自由链表(free lists),负责16种小型区块的次配置能力。内存池以malloc()配置而得。如果内存不足,则调用第一级配置器

  2. 如果需求大于128kb,也转调用第一级内存配置器。

第二级配置器维护了16个free_lists的结构,每一个free_lists的结构负责管理与之相关大小的内存区块。

各自管理的大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128 bytes的小额区块。

free_lists的节点结构如下:

union obj{
    union obj * free_list_link;   // for memory use
    char client_date[1]; 		  // for users use
};

第二级配置器的allocator的实现:

static void * allocate(size_t n)
  {
    obj * __VOLATILE * my_free_list;
    obj * __RESTRICT result;
    
    //要申请的空间大于128bytes就调用第一级配置
    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
    }
    //寻找 16 个free lists中恰当的一个
    my_free_list = free_list + FREELIST_INDEX(n);
    result = *my_free_list;
    if (result == 0) {
        //没找到可用的free list,准备新填充free list
        void *r = refill(ROUND_UP(n));
        return r;
    }
    *my_free_list = result -> free_list_link;
    return (result);
  };

从代码中,可以看出分为三种情况。

第一种,申请空间大于128bytes,则调用第一级配置器分配;

第二种,free_list中内存区块不足,需要从内存池中申请内存区块。

第三种,从free_list中获取合适的区块。

第一种情况就是直接调用默认的第一级配置器。

第二种情况是通过refill函数实现,下面会具体展开讲述。

第三种情况则是通过链表来实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZwnBdPv3-1590139655521)(C:\Users\andyhkmo\Desktop\学习笔记\photo\STL_allocator.png)]STL_Allocator内存配置器_第1张图片

第二级配置器的deallocator的实现:

static void deallocate(void *p, size_t n)
{
    obj *q = (obj *)p;
    obj * __VOLATILE * my_free_list;

    //如果要释放的字节数大于128,则调第一级配置器
    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;
}

从代码可以看到,施放过程比较简单,如果大于128bytes,调用第一级配置器free接口释放内存。

反之,把该区块回收到链表中。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mh9i2WZm-1590139655525)(C:\Users\andyhkmo\Desktop\学习笔记\photo\STL_allocator1.png)]STL_Allocator内存配置器_第2张图片

refill操作

在刚刚阐述的allocator分配内存操作中,当链表中不足以分配内存时,则需要通过refill操作去向内存池申请内存。而refill操作主要通过chunk_alloc去实现从内存池中取空间给free_list使用。

// 从内存池取出空间给free_list使用(第n号)(假定size已经适当调整为8的倍数)
char *alloc::chunk_alloc(size_t size, int &n_objs) 
{    
    char *result;
    size_t total_bytes = size * n_objs;              // 需要的内存总量
    size_t bytes_left = end_free - start_free;       // 内存池剩余空间

    if (bytes_left >= total_bytes) {
        // 内存池剩余空间空间完全满足需要
        result = start_free;
        /* 更新start_free指针 */
        start_free += total_bytes;

        return result;
    } else if (bytes_left >= size) {
        // 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块
        n_objs = bytes_left / size;    // 计算可以供应几个区块
        /* 更新start_free指针 */
        total_bytes = n_objs * size;
        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(更小的size的list);
            obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
            ((obj *) start_free)->free_list_link = *my_free_list;
            *my_free_list = (obj *) start_free;
        }
        // 配置heap空间, 用来补充内存池
        start_free = (char *) malloc(bytes_to_get);
        if (nullptr == start_free) {
            obj *p, **my_free_list;
            // heap空间不足,搜寻适当的free_list("未用区块,且区块够大")
            for (int i = size; i <= _MAX_BYTES; i += _ALIGN) {
                my_free_list = free_list + FREELIST_INDEX(i);
                p = *my_free_list;
                if (0 != p) {
                    *my_free_list = p->free_list_link;
                     start_free = (char *)p;
                     end_free = start_free + i;
                     return chunk_alloc(size, n_objs);
                }
           }
           end_free = 0;
        }
        heap_size += bytes_to_get;
        end_free = start_free + bytes_to_get;
        
        return chunk_alloc(size, n_objs);
   }
}

分为以下几种情况:

  1. 内存池中恰好有足够大的空间,则直接返回;

  2. 内存池中不能完全满足需求量,但是足够供应一个以上的区块,则返回足够的区块大小。

  3. 内存池中一个区块都不能够满足,

    3.1 首先把剩余的内存空间分配到合适的桶中(free_list)

    3.2 然后用malloc去申请2倍的需求内存大小。

    3.3 如果申请成功,则返回需求的内存空间大小。

    3.4 如果申请不成功,则需要从freelist(大于需求的块)中查看,是否有空闲的块还没使用,递归查询。

你可能感兴趣的:(C++)