STL学习(自学手册+源码分析)之空间配置器

1.1 空间配置器的标准接口

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
allocator::rebind(一个嵌套的class templateclass rebind< U >拥有唯一成员other,那是一个typedef 代表 allocator< U >)
allocator::allocator() 默认构造函数
allocator::allocator(const allocator&)拷贝构造
template< class U> allocator::allocator(const allocator< U >&)
泛化的拷贝构造 使用类型U来完成
allocator::~allocator() 
析构函数
pointer allocator::address(reference x) const
返回某个对象的地址
const_pointer allocator::address(reference x) const
返回某个对象的地址(此时无法赋值)
pointer allocator::allocate(size_type n,const void* = 0) 
配置空间,足以存储n个类型为T的对象。第二个参数是个提示。实现上可能会利用它来增进区域性(locality)或完全忽略
void allocator::deallocate(pointer p ,size_type n)
归还先前配置的空间
size_type allocator::max_size() const 
返回可成功配置的最大量
void allocater::construct(pointer p, const T& x)
等同于new(const void*)p) T(x)
void allocater::destory(pointer p)
等同于p->~T()

1.2 VC6实现的空间配置器

头文件<xmemory.h>

template<class _Ty>
	class allocator {
public:
	typedef _SIZT size_type;
	typedef _PDFT difference_type;
	typedef _Ty _FARQ *pointer;
	typedef const _Ty _FARQ *const_pointer;
	typedef _Ty _FARQ& reference;
	typedef const _Ty _FARQ& const_reference;
	typedef _Ty value_type;
	pointer address(reference _X) const
		{return (&_X); }
	const_pointer address(const_reference _X) const
		{return (&_X); }
	pointer allocate(size_type _N, const void *)
		{return (_Allocate((difference_type)_N, (pointer)0)); }
	char _FARQ *_Charalloc(size_type _N)
		{return (_Allocate((difference_type)_N,
			(char _FARQ *)0)); }
	void deallocate(void _FARQ *_P, size_type)
		{operator delete(_P); }
	void construct(pointer _P, const _Ty& _V)
		{_Construct(_P, _V); }
	void destroy(pointer _P)
		{_Destroy(_P); }
	_SIZT max_size() const
		{_SIZT _N = (_SIZT)(-1) / sizeof (_Ty);
		return (0 < _N ? _N : 1); }
	};

其中pointer allocate(size_type _N, const void *)实现调用了

template<class _Ty> inline
	_Ty _FARQ *_Allocate(_PDFT _N, _Ty _FARQ *)
	{if (_N < 0)
		_N = 0;
	return ((_Ty _FARQ *)operator new(
		(_SIZT)_N * sizeof (_Ty))); }
		// TEMPLATE FUNCTION _Construct

operator new()底层调用的是malloc()函数来实现的

对应的

void deallocate(void _FARQ *_P, size_type)

调用的是operator delete(_P),其底层调用的是C语言的free()函数实现。
除此之外没有任何特殊设计。

1.3 GNU C++实现的空间配置器

1.(未使用的版本 惠普(HP)版本)

头文件<defalloc.h>
G++<defalloc.h>中有这样的注释:

// DO NOT USE THIS FILE unless you have an old container implementation
// that requires an allocator with the HP-style interface.  
//
// Standard-conforming allocators have a very different interface.  The
// standard default allocator is declared in the header <memory>.
template <class _Tp>
class allocator {
public:
    typedef _Tp value_type;
    typedef _Tp* pointer;
    typedef const _Tp* const_pointer;
    typedef _Tp& reference;
    typedef const _Tp& 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(_Tp))); 
    }
    size_type max_size() const { 
	return max(size_type(1), size_type(UINT_MAX/sizeof(_Tp))); 
    }
};

其中重点关注pointer allocate(size_type __n)void deallocate(pointer __p)的实现。

pointer allocate(size_type __n)的 实现如下:

template <class _Tp>
inline _Tp* allocate(ptrdiff_t __size, _Tp*) {
    set_new_handler(0);
    _Tp* __tmp = (_Tp*)(::operator new((size_t)(__size * sizeof(_Tp))));
    if (__tmp == 0) {
	cerr << "out of memory" << endl; 
	exit(1);
    }
    return __tmp;
}

其中也是调用了::operator new()(底层是malloc()函数)来实现的,没有做任何特殊设计。

void deallocate(pointer __p)的实现如下:

template <class _Tp>
inline void deallocate(_Tp* __buffer) {
    ::operator delete(__buffer);
}

其中调用了operator delete()(底层为free())实现,没有做任何特殊设计。

总结:直接使用malloc()free()实现的分配器,如果在元素本身占用空间较小的情况下,会造成许多额外的开销(具体可以参考malloc()分配内存的方式(强推侯捷《内存管理》)),故不建议直接使用。

1.2.SGI实现的分配器

头文件<stl_alloc.h>
该版本尽量减少malloc的次数。
考虑到小型区块所可能造成的内存破碎的问题,SGI设计了双层配置器,第一级配置器直接使用malloc()free(),第二级配置器则视情况采用不同的策略:当配置区块超过128bytes时,视为“足够大”,此时调用第一级配置器;
当配置器小于128bytes时,视为“过小”,为降低额外负担,便采用复杂的memory pool整理方式,而不再求助于第一级配置器。

template<class _Tp, class _Alloc>
class simple_alloc {

public:
    static _Tp* allocate(size_t __n)
      { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }
    static _Tp* allocate(void)
      { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }
    static void deallocate(_Tp* __p, size_t __n)
      { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }
    static void deallocate(_Tp* __p)
      { _Alloc::deallocate(__p, sizeof (_Tp)); }
};

其内部四个成员函数其实都是单纯的转调用,调用传递给配置器(可能是第一级也可能是第二级)的成员函数。这个接口使配置器的配置单位从bytes转为个别元素的大小sizeof(T))。SGI STL容器全部使用这个simple_alloc接口。(vector)就是。

一、二级配置器的关系、接口包装、实际运用方式如下图:
在这里插入图片描述
其中第一级配置器:

template <int __inst>
class __malloc_alloc_template {

private:

  static void* _S_oom_malloc(size_t);
  static void* _S_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 = _S_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 = _S_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);
  }

};

可以发现24行的deallocate()直接调用的是free()
再关注其中第18行的_S_oom_malloc(__n),其实现:


template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_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);
    }
}

可以发现其底层使用的是malloc()函数调用。

其中第二级配置器:

template <bool threads, int inst>
class __default_alloc_template {

private:
  // Really we should use static const int x = N
  // instead of enum { x = N }, but few compilers accept the former.
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))
    enum {_ALIGN = 8};
    enum {_MAX_BYTES = 128};
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN
# endif
  static size_t
  _S_round_up(size_t __bytes) 
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }

__PRIVATE:
  union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };
private:
# if defined(__SUNPRO_CC) || defined(__GNUC__) || defined(__HP_aCC)
    static _Obj* __STL_VOLATILE _S_free_list[]; 
        // Specifying a size results in duplicate def for 4.1
# else
    static _Obj* __STL_VOLATILE _S_free_list[_NFREELISTS]; 
# endif
  static  size_t _S_freelist_index(size_t __bytes) {
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }

  // Returns an object of size __n, and optionally adds to size __n free list.
  static void* _S_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.
  static char* _S_chunk_alloc(size_t __size, int& __nobjs);

  // Chunk allocation state.
  static char* _S_start_free;
  static char* _S_end_free;
  static size_t _S_heap_size;

# ifdef __STL_THREADS
    static _STL_mutex_lock _S_node_allocator_lock;
# endif

    // It would be nice to use _STL_auto_lock here.  But we
    // don't need the NULL check.  And we do need a test whether
    // threads have actually been started.
    class _Lock;
    friend class _Lock;
    class _Lock {
        public:
            _Lock() { __NODE_ALLOCATOR_LOCK; }
            ~_Lock() { __NODE_ALLOCATOR_UNLOCK; }
    };

public:

  /* __n must be > 0      */
  static void* allocate(size_t __n)
  {
    void* __ret = 0;

    if (__n > (size_t) _MAX_BYTES) {
      __ret = malloc_alloc::allocate(__n);
    }
    else {
      _Obj* __STL_VOLATILE* __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      // 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
      _Obj* __RESTRICT __result = *__my_free_list;
      if (__result == 0)
        __ret = _S_refill(_S_round_up(__n));
      else {
        *__my_free_list = __result -> _M_free_list_link;
        __ret = __result;
      }
    }

    return __ret;
  };
  
  /* __p may not be 0 */
  static void deallocate(void* __p, size_t __n)
  {
    if (__n > (size_t) _MAX_BYTES)
      malloc_alloc::deallocate(__p, __n);
    else {
      _Obj* __STL_VOLATILE*  __my_free_list
          = _S_free_list + _S_freelist_index(__n);
      _Obj* __q = (_Obj*)__p;

      // acquire lock
#       ifndef _NOTHREADS
      /*REFERENCED*/
      _Lock __lock_instance;
#       endif /* _NOTHREADS */
      __q -> _M_free_list_link = *__my_free_list;
      *__my_free_list = __q;
      // lock is released here
    }
  }

关注61行以及91行,其中allocate(size_t __n)会根据__n的大小来选择使用第一级配置器还是第二级配置器,第二级配置器维护了16个自由链表(free_list),负责16种小型区块的次配置能力。内存池(memory pool)malloc()配置而得。
deallocate()也会根据是情况使用第一级配置器的free()或者第二级配置器的内存释放处理。

一点小问题

第二级配置器第17行:

union _Obj {
        union _Obj* _M_free_list_link;
        char _M_client_data[1];    /* The client sees this.        */
  };

书上给的解释是这样的:“上述obj所用用的是union,由于union之故,从其第一字段观之,obj可被视为—个指针,指向相同形式的另一个obj。从其第二字段观之,obj可被视为一个指针,指向实际区块。一物二用的结果是,不会为了维护链表所必须的指针而造成内存的另一种浪费。
第一句话很好理解,在此就不再赘述,本文将探讨的重点放在第二句话,即其为何指向实际区块。
现在看下面一段代码的运行结果:
在这里插入图片描述
如图所示,指针p所存的地址为0x008ff7a0即为数组m1首元素地址,同时data首元素地址与数组m首元素地址相同。
对此,我的理解是:
此时test = (union obj*)m;
使得test指向数组m所在空间,则data[0]所占空间为m[0]所占空间,于是data所指空间即为m所指空间。
由上述可知在第二级空间配置器中,自由链表某节点client_data所指地址即为该区块首地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值