STL alloc源码分析 sgi-2.91版本 (一)

先理解分配器原理->内存分配,再看源码分析。

一级分配器

当二级分配器分配内存失败时再调用一级分配器,4.9版本后去掉一级分配器,本文章主要分析二级分配器的内存池机制。

//一级分配器
template <int inst>
class __malloc_alloc_template {

private:

static void *oom_malloc(size_t);

static void *oom_realloc(void *, size_t);

#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
    static void (* __malloc_alloc_oom_handler)();
#endif

public:

static void * allocate(size_t n)
{
    void *result = malloc(n);
    if (0 == result) result = oom_malloc(n);
    return result;
}

static void deallocate(void *p, size_t /* n */)
{
    free(p);
}

static void * 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;
}

static void (* set_malloc_handler(void (*f)()))()
{
    void (* old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = f;
    return(old);
}

};
//分析内存分配函数,没什么机制,就是单纯调用malloc
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);			//调用malloc开辟空间
        if (result) return(result);		//返回地址指针
    }
}

simple_alloc

换肤工程,其实就是对alloc的简单包装,容器中会使用这个间接调用alloc

//容器内部会有这个声明
template <class T, class Alloc = alloc>
class vector {
	//.....
	//.....
protected:
	typedef simple_alloc<value_type, Alloc> data_allocator;
}
//容器中函数的调用方式:data_allocator::allocate

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

二级分配器alloc(主要分析)

首先记几个名称的type,

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
typedef __default_alloc_template<false, 0> single_client_alloc;

复习一下内存分配的图
在这里插入图片描述

二级分配器__default_alloc_template,具体功能见代码注释

template <bool threads, int inst>
class __default_alloc_template {
private:
    enum {__ALIGN = 8};   //上调边界,每个区号多占8字节,#0对应8,#1对应16
    enum {__MAX_BYTES = 128};  //最大单个快内存128个字节
    enum {__NFREELISTS = __MAX_BYTES/__ALIGN};    //区块上限:123/8=16,意思是#0到#15共16个区号
    
  static size_t ROUND_UP(size_t bytes) {
        return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1));
        //字节对齐算法,8字节对齐,原理很简单,7取反0x11111000,
        //每次&操作置最低三位为0,自然满足8字节对齐
  }
__PRIVATE:
  union obj {
        union obj * free_list_link;   
        //嵌入式指针,分配时这个指针指向下一个分配块,称为free_list_link
        //此指针和数据共享内存,当有数据写入时自动被覆盖
        char client_data[1];    /* The client sees this. 这个其实可以拿掉       */
  };
private:
# ifdef __SUNPRO_CC
    static obj * __VOLATILE free_list[];  //默认值,不用在意
        // Specifying a size results in duplicate def for 4.1
# else
    static obj * __VOLATILE free_list[__NFREELISTS];  //分配16个区号,即链表#0到#15
# endif
  static  size_t FREELIST_INDEX(size_t bytes) {
        return (((bytes) + __ALIGN-1)/__ALIGN - 1);
        //向上取整算法,决定bytes的空间由哪个区号链表负责
        //栗子:(57+7)/8-1=7,57字节空间由#7区号链表负责分配,因为#7号链表上每一块为64字节。
  }

  // Returns an object of size n, and optionally adds to size n free list.
  //返回大小为n的对象,并可选地添加到大小为n的空闲列表中。
  static void *refill(size_t n);
  
  // Allocates a chunk for nobjs of size "size".  nobjs may be reduced
  // if it is inconvenient to allocate the requested number.
  //为大小为“size”的nobjs分配一个块。nobjs可能会减少
  //如果分配所要求的号码不方便。返回大小为n的对象,并可选地添加到大小为n的空闲列表中。
  static char *chunk_alloc(size_t size, int &nobjs);//分配内存块操作

  // Chunk allocation state.
  static char *start_free;  //指向备用池的头
  static char *end_free;    //指向备用池的尾
  static size_t heap_size;  //分配空间总字节数
/*下面涉及到多线程,不作分析,跳到allocate函数进行分析*/
# ifdef __STL_SGI_THREADS
    static volatile unsigned long __node_allocator_lock;
    static void __lock(volatile unsigned long *); 
    static inline void __unlock(volatile unsigned long *);
# endif

# ifdef __STL_PTHREADS
    static pthread_mutex_t __node_allocator_lock;
# endif

# ifdef __STL_WIN32THREADS
    static CRITICAL_SECTION __node_allocator_lock;
    static bool __node_allocator_lock_initialized;

  public:
    __default_alloc_template() {
	// This assumes the first constructor is called before threads
	// are started.
        if (!__node_allocator_lock_initialized) {
            InitializeCriticalSection(&__node_allocator_lock);
            __node_allocator_lock_initialized = true;
        }
    }
  private:
# endif

    class lock {
        public:
            lock() { __NODE_ALLOCATOR_LOCK; }
            ~lock() { __NODE_ALLOCATOR_UNLOCK; }
    };
    friend class lock;
/*以上涉及多线程*/
public:

  /* n must be > 0      */
  static void * allocate(size_t n)  ///分配内存函数
  {
		//见下面分析
  };

  /* p may not be 0 */
  static void deallocate(void *p, size_t n)  //回收内存函数,注意此处并没有还给系统	
  {
    //见下面分析
  }

  static void * reallocate(void *p, size_t old_sz, size_t new_sz);

} ;

allocate函数

/* n must be > 0      */
  static void * allocate(size_t n)  ///分配内存函数
  {
  	//有定义 #define __VOLATILE volatile, volatile涉及多线程,也可以拿掉看待
    obj * __VOLATILE * my_free_list;    // obj ** my_free_list ,意为指向区号链表的指针
    obj * __RESTRICT result;

    if (n > (size_t) __MAX_BYTES) {
        return(malloc_alloc::allocate(n));
        //如果申请内存大于128字节,那么交给第一级分配器处理
    }
    
    my_free_list = free_list + FREELIST_INDEX(n);
	/*1.free_list指向区块链表的头部,即#0位置
	  2.FREELIST_INDEX(n)分析见上面,计算出由哪块区号链表负责这块内存
	  3.my_free_list 最后指向那块区号链表位置。
	*/
	
    // Acquire the lock here with a constructor call.
    // This ensures that it is released in exit or during stack
    // unwinding.
#       ifndef _NOTHREADS
        /*REFERENCED*/
        lock lock_instance;  //线程加锁
#       endif

    result = *my_free_list;   
    //*my_free_list取得相应区号链表中的值,其值也是个地址,指向这个区号链表负责的内存。
    //result此时指向这块内存的头部
    
    if (result == 0) {     //如果指向的内存没有分配空间的话,执行分配空间操作
        void *r = refill(ROUND_UP(n)); //填充free_list,内部调用malloc,返回区块的起始地址,
        //详细分析见alloc源码分析  sgi-2.91版本  (二)。
        //ROUND_UP函数8字节对齐
        return r;
    }
    *my_free_list = result -> free_list_link;
    //free_list_link指向下一块可用内存
    //此时result会返回给容器push数据,*my_free_list也就是区号链表中的地址改为指向下一块可用内存,为下一次容器push做准备。 
    return (result);
  };

  

结合图理解,图来自侯捷老师

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

deallocate函数

/* p may not be 0 */
  static void 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);  
    //my_free_list 指向相应区号链表
  
    // acquire lock
#       ifndef _NOTHREADS
        /*REFERENCED*/
        lock lock_instance;      //多线程加锁
#       endif /* _NOTHREADS */
    q -> free_list_link = *my_free_list;
	//*my_free_list取区号链表存储的地址,是一个指向该区号负责的内存中可用空间的地址
	//q -> free_list_link赋值,因此q内存指向的下一个区域是一块可用空间
	
    *my_free_list = q;  
    //此时区号链表中存储的地址变为q
    //下一刻容器push时就会覆盖本次处理中q指向的原来数据的内存,
    //此时可以理解这个函数的回收本质就是覆盖,你可以使用那些你标记为回收成功的空间。
    //free_list_link的存在可以保证分配空间中的未用空间不会丢失地址。


    // lock is released here
  }

关于回收函数这个图,存疑,侯捷老师认为是头插法,但上面实际的代码分析并不是,
my_free_list = free_list + FREELIST_INDEX(n);
*my_free_list取得地址是指向一块可用空间,并不指向内存块头部 。
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值