三种的allocator实现源代码的对比

本文对比分析了ACE、SGI和MS的allocator实现,详细探讨了它们在内存分配、接口实现和设计上的区别。通过阅读侯捷先生的《STL源码剖析》,对allocator有了更深入的理解,特别是SGI的内存池管理和ACE的非标准但高效的实现。作者强调,通过不断学习和实践,可以逐步掌握原本看似高深的技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      最近看空间配置器的内容,把ACE的ACE_Allocator类实现,SGI的allocator类实现和MS的allocator实现也参考了侯捷先生的《STL源码剖析》,有不少收获。

      我听说是有说明STL中allocator实现标准的文件,但我没有找到,据我实验推测,标准allocator需要实现rebind,allocate,deallocate,max_size和构造及析构函数一共六个函数。也就是说,我要写一个在标准vector可用的allocator最小只需要上面的几个接口实现就可以了。

      先来说一下微软的allocator。文件名是xmemory,我觉得是最没有看头的,基本就是new和delete的封装,为了迎合C++标准库的标准做的。没有什么技巧,更别说微妙了。上面的六个接口下面都有实现。

		// TEMPLATE CLASS allocator
template<class _Ty>
	class allocator
		: public _Allocator_base<_Ty>
	{	// generic allocator for objects of class _Ty
public:
	typedef _Allocator_base<_Ty> _Mybase;
	typedef typename _Mybase::value_type value_type;
	typedef value_type _FARQ *pointer;
	typedef value_type _FARQ& reference;
	typedef const value_type _FARQ *const_pointer;
	typedef const value_type _FARQ& const_reference;

	typedef _SIZT size_type;
	typedef _PDFT difference_type;

	template<class _Other>
		struct rebind
		{	// convert an allocator<_Ty> to an allocator <_Other>
		typedef allocator<_Other> other;
		};

	pointer address(reference _Val) const
		{	// return address of mutable _Val
		return (&_Val);
		}

	const_pointer address(const_reference _Val) const
		{	// return address of nonmutable _Val
		return (&_Val);
		}

	allocator() _THROW0()
		{	// construct default allocator (do nothing)
		}

	allocator(const allocator<_Ty>&) _THROW0()
		{	// construct by copying (do nothing)
		}

	template<class _Other>
		allocator(const allocator<_Other>&) _THROW0()
		{	// construct from a related allocator (do nothing)
		}

	template<class _Other>
		allocator<_Ty>& operator=(const allocator<_Other>&)
		{	// assign from a related allocator (do nothing)
		return (*this);
		}

	void deallocate(pointer _Ptr, size_type)
		{	// deallocate object at _Ptr, ignore size
		::operator delete(_Ptr);
		}

	pointer allocate(size_type _Count)
		{	// allocate array of _Count elements
		return (_Allocate(_Count, (pointer)0));
		}

	pointer allocate(size_type _Count, const void _FARQ *)
		{	// allocate array of _Count elements, ignore hint
		return (allocate(_Count));
		}

	void construct(pointer _Ptr, const _Ty& _Val)
		{	// construct object at _Ptr with value _Val
		_Construct(_Ptr, _Val);
		}

	void destroy(pointer _Ptr)
		{	// destroy object at _Ptr
		_Destroy(_Ptr);
		}

	_SIZT max_size() const _THROW0()
		{	// estimate maximum array size
		_SIZT _Count = (_SIZT)(-1) / sizeof (_Ty);
		return (0 < _Count ? _Count : 1);
		}
	};

 

      2. SGI STL实现的allocator。作为C++作者都主推的STL实现版本,当然是符合标准的。它的主站:http://www.sgi.com/tech/stl/ ,怎么去配置调试我已经在上一篇讲过了。它的实现通过阅读侯捷先生的书得到更深入的了解。当然代码与侯先生解析的那个版本有一些不同,无非是加了一些代理以及包装之类的,影响不大。我们可以看到这些接口大都通过__sgi_alloc中的函数去实现。

template <class _Tp>
struct __stlport_class
{ typedef _Tp _Type; };

template <class _Tp>
class allocator //: public _AllocatorAux<_Tp>
/* A small helper struct to recognize STLport allocator implementation
 * from any user specialization one.
 */
                : public __stlport_class<allocator<_Tp> >
{
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;
#if defined (_STLP_MEMBER_TEMPLATE_CLASSES)
  template <class _Tp1> struct rebind {
    typedef allocator<_Tp1> other;
  };
#endif
  allocator() _STLP_NOTHROW {}
#if defined (_STLP_MEMBER_TEMPLATES)
  template <class _Tp1> allocator(const allocator<_Tp1>&) _STLP_NOTHROW {}
#endif
  allocator(const allocator<_Tp>&) _STLP_NOTHROW {}
#if !defined (_STLP_NO_MOVE_SEMANTIC)
  allocator(__move_source<allocator<_Tp> > src) _STLP_NOTHROW {}
#endif
  ~allocator() _STLP_NOTHROW {}
  pointer address(reference __x) const {return &__x;}
  const_pointer address(const_reference __x) const { return &__x; }
  // __n is permitted to be 0.  The C++ standard says nothing about what the return value is when __n == 0.
  _Tp* allocate(size_type __n, const void* = 0) {
    if (__n > max_size()) {
      _STLP_THROW_BAD_ALLOC;
    }
    if (__n != 0) {
      size_type __buf_size = __n * sizeof(value_type);
      _Tp* __ret = __REINTERPRET_CAST(_Tp*, __sgi_alloc::allocate(__buf_size));
#if defined (_STLP_DEBUG_UNINITIALIZED) && !defined (_STLP_DEBUG_ALLOC)
      memset((char*)__ret, _STLP_SHRED_BYTE, __buf_size);
#endif
      return __ret;
    }

    return 0;
  }
  // __p is permitted to be a null pointer, only if n==0.
  void deallocate(pointer __p, size_type __n) {
    _STLP_ASSERT( (__p == 0) == (__n == 0) )
    if (__p != 0) {
#if defined (_STLP_DEBUG_UNINITIALIZED) && !defined (_STLP_DEBUG_ALLOC)
      memset((char*)__p, _STLP_SHRED_BYTE, __n * sizeof(value_type));
#endif
      __sgi_alloc::deallocate((void*)__p, __n * sizeof(value_type));
    }
  }
#if !defined (_STLP_NO_ANACHRONISMS)
  // backwards compatibility
  void deallocate(pointer __p) const {  if (__p != 0) __sgi_alloc::deallocate((void*)__p, sizeof(value_type)); }
#endif
  size_type max_size() const _STLP_NOTHROW  { return size_t(-1) / sizeof(value_type); }
  void construct(pointer __p, const_reference __val) { _STLP_STD::_Copy_Construct(__p, __val); }
  void destroy(pointer __p) { _STLP_STD::_Destroy(__p); }

#if defined (_STLP_NO_EXTENSIONS)
  /* STLport extension giving rounded size of an allocated memory buffer
   * This method do not have to be part of a user defined allocator implementation
   * and won't even be called if such a function was granted.
   */
protected:
#endif
  _Tp* _M_allocate(size_type __n, size_type& __allocated_n) {
    if (__n > max_size()) {
      _STLP_THROW_BAD_ALLOC;
    }

    if (__n != 0) {
      size_type __buf_size = __n * sizeof(value_type);
      _Tp* __ret = __REINTERPRET_CAST(_Tp*, __sgi_alloc::allocate(__buf_size));
#if defined (_STLP_DEBUG_UNINITIALIZED) && !defined (_STLP_DEBUG_ALLOC)
      memset((char*)__ret, _STLP_SHRED_BYTE, __buf_size);
#endif
      __allocated_n = __buf_size / sizeof(value_type);
      return __ret;
    }

    return 0;
  }
#if defined (_STLP_USE_PARTIAL_SPEC_WORKAROUND) && !defined (_STLP_FUNCTION_TMPL_PARTIAL_ORDER)
  void _M_swap_workaround(allocator<_Tp>& __other) {}
#endif
};


       在我调试的时候是用内存分配函数_M_allocate来从内存池(按侯先生的说法是空间,不一定是内存)中分配可用空间到自由链以及返回用户使用。若想更进一步了解,必须自己去看源代码:RTFSC。小结一下,SGI 的这份代码符合标准规范,结合侯先生的书,可以让你看清STL的实现本质。

 

      3. 最后说一下ACE的allocator实现。应该说,ACE的实现可能在设计的时候,就不打算遵守C++标准库的规范,只是为了高效安全的在ACE内部使用。我们也可以看以下接口代码。基类ACE_Allocator直接使用了malloc和free让子类去实现。这份代码完全可以结合侯先生的书来看,只是在一些实现的名字前面加上ACE或者_S等前缀,实现的原理和SGI是很相似的。在内存块管理方面,小块内存(小于128),也是用自由链去管理,大块内存(大于128)直接分配。在自由链表方面它也使用了一个和SGI一样的小技巧,就是把next指针放在未使用内存块的开头处(我第一次看到这种技巧,有点怪怪的,但是能很好的实现,主要是效率有提升,多少就不考究了)。比SGI加多了一个block块链的管理,可以更灵活的使用(应该是限于ACE的应用了,因为它不遵守标准)。

class ACE_Export ACE_Allocator
{
public:

  /// Unsigned integer type used for specifying memory block lengths.
  typedef size_t size_type;

  // = Memory Management

  /// Get pointer to a default ACE_Allocator.
  static ACE_Allocator *instance (void);

  /// Set pointer to a process-wide ACE_Allocator and return existing
  /// pointer.
  static ACE_Allocator *instance (ACE_Allocator *);

  /// Delete the dynamically allocated Singleton
  static void close_singleton (void);

  /// "No-op" constructor (needed to make certain compilers happy).
  ACE_Allocator (void);

  /// Virtual destructor
  virtual ~ACE_Allocator (void);

  /// Allocate @a nbytes, but don't give them any initial value.
  virtual void *malloc (size_type nbytes) = 0;

  /// Allocate @a nbytes, giving them @a initial_value.
  virtual void *calloc (size_type nbytes, char initial_value = '\0') = 0;

  /// Allocate <n_elem> each of size @a elem_size, giving them
  /// @a initial_value.
  virtual void *calloc (size_type n_elem,
                        size_type elem_size,
                        char initial_value = '\0') = 0;

  /// Free <ptr> (must have been allocated by <ACE_Allocator::malloc>).
  virtual void free (void *ptr) = 0;

  /// Remove any resources associated with this memory manager.
  virtual int remove (void) = 0;

  // = Map manager like functions

  /**
   * Associate @a name with @a pointer.  If @a duplicates == 0 then do
   * not allow duplicate @a name/@a pointer associations, else if
   * @a duplicates != 0 then allow duplicate @a name/@a pointer
   * assocations.  Returns 0 if successfully binds (1) a previously
   * unbound @a name or (2) @a duplicates != 0, returns 1 if trying to
   * bind a previously bound @a name and @a duplicates == 0, else
   * returns -1 if a resource failure occurs.
   */
  virtual int bind (const char *name, void *pointer, int duplicates = 0) = 0;

  /**
   * Associate @a name with @a pointer.  Does not allow duplicate
   * @a name/@a pointer associations.  Returns 0 if successfully binds
   * (1) a previously unbound @a name, 1 if trying to bind a previously
   * bound @a name, or returns -1 if a resource failure occurs.  When
   * this call returns @a pointer's value will always reference the
   * void * that @a name is associated with.  Thus, if the caller needs
   * to use @a pointer (e.g., to free it) a copy must be maintained by
   * the caller.
   */
  virtual int trybind (const char *name, void *&pointer) = 0;

  /// Locate @a name and pass out parameter via pointer.  If found,
  /// return 0, returns -1 if failure occurs.
  virtual int find (const char *name, void *&pointer) = 0;

  /// Returns 0 if the name is in the mapping. -1, otherwise.
  virtual int find (const char *name) = 0;

  /// Unbind (remove) the name from the map.  Don't return the pointer
  /// to the caller
  virtual int unbind (const char *name) = 0;

  /// Break any association of name.  Returns the value of pointer in
  /// case the caller needs to deallocate memory.
  virtual int unbind (const char *name, void *&pointer) = 0;

  // = Protection and "sync" (i.e., flushing memory to persistent
  // backing store).

  /**
   * Sync @a len bytes of the memory region to the backing store
   * starting at @c this->base_addr_.  If @a len == -1 then sync the
   * whole region.
   */
  virtual int sync (ssize_t len = -1, int flags = MS_SYNC) = 0;

  /// Sync @a len bytes of the memory region to the backing store
  /// starting at @a addr.
  virtual int sync (void *addr, size_type len, int flags = MS_SYNC) = 0;

  /**
   * Change the protection of the pages of the mapped region to @a prot
   * starting at <this->base_addr_> up to @a len bytes.  If @a len == -1
   * then change protection of all pages in the mapped region.
   */
  virtual int protect (ssize_t len = -1, int prot = PROT_RDWR) = 0;

  /// Change the protection of the pages of the mapped region to @a prot
  /// starting at @a addr up to @a len bytes.
  virtual int protect (void *addr, size_type len, int prot = PROT_RDWR) = 0;

#if defined (ACE_HAS_MALLOC_STATS)
  /// Dump statistics of how malloc is behaving.
  virtual void print_stats (void) const = 0;
#endif /* ACE_HAS_MALLOC_STATS */

  /// Dump the state of the object.
  virtual void dump (void) const = 0;
private:
  // DO NOT ADD ANY STATE (DATA MEMBERS) TO THIS CLASS!!!!  See the
  // <ACE_Allocator::instance> implementation for explanation.

  /// Pointer to a process-wide ACE_Allocator instance.
  static ACE_Allocator *allocator_;

  /// Must delete the <allocator_> if non-0.
  static int delete_allocator_;
};


      最近自己也写了几个allocator,还没有研究更好的实现方式。不过看上去,侯先生书上说空间配置器,我应该可以考虑一下读取硬盘空间来做allocator的空间,虚拟内存估计就是这么实现的吧。

     随想:回想一年前,我第一次使用标准库,觉得allocator实现是很高深的学问,自己什么时候才能学会啊。后来看侯先生的allocator这本书,觉得我也可以做到,但由于自己的懒惰,很久都没有实践,最近有时间,再把标准库认认真真的读一下,写一些深得体会,也对得起自己这三年的工作学习。其实有很多事情,一开始觉得那么高深而自己难为之,只要有信心,方法用对了,坚持下来就会有突破的,而且一旦突破,那种快乐是相当舒服的。

     最近有一个用了C++快五年的程序员,对C++及开源的了解相当深入,我觉得他也是一步一步走过来的。对于一些元编程,模板的灵活用法,网络编程的高级使用,服务器的负载均衡,linux内核机制,window底层原理,他都有所深入了解,他现在说的很多我都还不懂,我需要坚持自己的步伐,加快一点。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值