STL stack allocate
游戏编程精粹3提供了一份栈分配器源代码:
#include <memory> #include <limits> template <typename T> class StackAlloc { public: // Typedefs typedef size_t size_type; typedef ptrdiff_t difference_type; typedef T* pointer; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef T value_type; // Constructors StackAlloc() throw() : mpStack( NULL ), mBytesAllocated( 0 ), mMaxBytes( 0 ) { } StackAlloc( unsigned char* pStack, size_t nMaxBytes ) throw() : mpStack( pStack ), mBytesAllocated( 0 ), mMaxBytes( nMaxBytes ) { } StackAlloc( const StackAlloc& sa ) throw() : mpStack( sa.mpStack ), mBytesAllocated( 0 ), mMaxBytes( sa.mMaxBytes ) { // Copying the "stack" resets mBytesAllocated to zero } #if _MSC_VER >= 1400 // VC 7 can't handle template members template <typename U> StackAlloc( const StackAlloc<U>& sa ) throw() : mpStack( sa.mpStack ), mBytesAllocated( 0 ), mMaxBytes( sa.mMaxBytes ) { // Copying the "stack" resets mBytesAllocated to zero } #endif StackAlloc& operator=( const StackAlloc& sa ) { // Copying the "stack" resets mBytesAllocated to zero mpStack = sa.mpStack; mBytesAllocated = 0; mMaxBytes = sa.mMaxBytes; return *this; } // Destructor ~StackAlloc() throw() { } // Utility functions pointer address( reference r ) const { return &r; } const_pointer address( const_reference c ) const { return &c; } size_type max_size() const { return std::numeric_limits<size_t>::max() / sizeof(T); } // In-place construction void construct( pointer p, const_reference c ) { // placement new operator new( reinterpret_cast<void*>(p) ) T(c); } // In-place destruction void destroy( pointer p ) { // call destructor directly (p)->~T(); } // Rebind to allocators of other types template <typename U> struct rebind { typedef StackAlloc<U> other; }; // Allocate raw memory pointer allocate( size_type n, const void* = NULL ) { void* pRaw = mpStack + mBytesAllocated; mBytesAllocated += ( n * sizeof(T) ); if( mBytesAllocated+1 > mMaxBytes ) throw std::bad_alloc(); return pointer(pRaw); } // Free raw memory. // Note that C++ standard defines this function as // deallocate( pointer p, size_type). Because Visual C++ 6.0 // compiler doesn't support template rebind, Dinkumware uses // void* hack. void deallocate( void*, size_type ) { // No need to free stack memory } // Non-standard Dinkumware hack for Visual C++ 6.0 compiler. // VC 6 doesn't support template rebind. char* _Charalloc( size_type n ) { return reinterpret_cast<char*>( allocate( n, NULL ) ); } // Required for global comparison functions unsigned char* GetStack() const { return mpStack; } private: unsigned char* mpStack; size_t mBytesAllocated; size_t mMaxBytes; }; // end of StackAlloc // Comparison template <typename T1> bool operator==( const StackAlloc<T1>& lhs, const StackAlloc<T1>& rhs) throw() { return lhs.GetStack() == rhs.GetStack(); } template <typename T1> bool operator!=( const StackAlloc<T1>& lhs, const StackAlloc<T1>& rhs) throw() { return lhs.GetStack() != rhs.GetStack(); }
测试发现在VS2012下编译运行,栈分配器在释放内存时出错。
原因是当用户提供自定义的分配器时,VS会保存一个分配策略对象来管理计数神马的。
部分VS源代码如下:
_Vector_alloc(const _Alty& _Al = _Alty()) : _Alval(_Al) { // construct allocator from _Al _Alloc_proxy(); } //------------------------------------------------------------- void _Alloc_proxy() { // construct proxy from _Alval typename _Alloc::template rebind<_Container_proxy>::other _Alproxy(_Alval); this->_Myproxy = _Alproxy.allocate(1); _Alproxy.construct(this->_Myproxy, _Container_proxy()); this->_Myproxy->_Mycont = this; }
vector的栈分配器是_Alval,也就是用户提供的栈分配器。
分配策略对象_Myproxy占用的内存也是通过_Alval申请的。仅仅是这样也许不会出现神马问题,可是_Myproxy是通过调用template <typename U> StackAlloc( const StackAlloc<U>& sa )重绑定函数从_Alval获取了一个新的分配器对象_Alproxy。此时_Alval与_Alproxy其实是共用一段内存的,而彼此不知道对方的存在。导致_Myproxy使用的内存在vector插入元素时被覆盖。当vector释放内存或者迁移内存时需要释放掉_Myproxy的内存时程序就崩溃了。
实际上游戏编程精粹3提供的stack allocate在语义上就存在问题,mpstack不应该是值语义的,因为stack allocate实际上即没有真正向系统申请内存,也并没有真正的释放内存,而只是代为管理一段原本就存在的内存。因此,mpstack应该是引用语义的。
现将本人修改后的代码献上:
PS:只在VS2012下测试了vector与list容器。
1 template<typename T> 2 class stack_alloc 3 { 4 public: 5 typedef size_t size_type; 6 typedef ptrdiff_t difference_type; 7 typedef T* pointer; 8 typedef const T* const_pointer; 9 typedef T& reference; 10 typedef const T& const_reference; 11 typedef T value_type; 12 13 public: 14 stack_alloc() throw() 15 : m_begin(NULL) 16 , m_cur(m_begin) 17 , m_max_bytes(0) 18 { 19 } 20 21 stack_alloc(uchar* pstack, size_t max_bytes) throw() 22 : m_begin(pstack) 23 , m_cur(m_begin) 24 , m_max_bytes(max_bytes) 25 { 26 } 27 28 stack_alloc(const stack_alloc& sa) throw() 29 : m_begin(sa.m_begin) 30 , m_cur(sa.m_cur) 31 , m_max_bytes(sa.m_max_bytes) 32 { 33 } 34 35 #if _MSC_VER >= 1400 // VC 7 can't handle template members 36 template <typename U> 37 stack_alloc(const stack_alloc<U>& sa) throw() 38 : m_begin(sa.m_begin) 39 , m_cur(sa.m_cur) 40 , m_max_bytes(sa.m_max_bytes) 41 { 42 } 43 #endif 44 45 stack_alloc& operator=( const stack_alloc& rhs ) 46 { 47 return *this; 48 } 49 50 ~stack_alloc() throw() 51 { 52 } 53 54 public: 55 // Utility functions 56 pointer address( reference r ) const 57 { 58 return &r; 59 } 60 61 const_pointer address( const_reference c ) const 62 { 63 return &c; 64 } 65 66 size_type max_size() const 67 { 68 return m_max_bytes/sizeof(T); 69 } 70 71 // In-place construction 72 void construct( pointer p, const_reference c ) 73 { 74 new( reinterpret_cast<void*>(p) ) T(c); 75 } 76 77 // In-place destruction 78 void destroy( pointer p ) 79 { 80 (p)->~T(); 81 } 82 83 // Rebind to allocators of other types 84 template <typename U> 85 struct rebind 86 { 87 typedef stack_alloc<U> other; 88 }; 89 90 // Allocate raw memory 91 pointer allocate( size_type n, const void* = NULL ) 92 { 93 void* praw = m_cur; 94 m_cur += n*sizeof(T); 95 if(m_cur+1>m_begin+m_max_bytes) 96 { 97 throw std::bad_alloc(); 98 } 99 100 return pointer(praw); 101 } 102 103 void deallocate( void* p, size_type n) 104 { 105 } 106 107 // Non-standard Dinkumware hack for Visual C++ 6.0 compiler. 108 // VC 6 doesn't support template rebind. 109 char* _charalloc( size_type n ) 110 { 111 return reinterpret_cast<char*>( allocate( n, NULL ) ); 112 } 113 114 unsigned char* get_stack() const 115 { 116 return m_begin; 117 } 118 119 uchar*& m_cur; 120 size_t m_max_bytes; 121 uchar* m_begin; 122 }; 123 124 template <typename T> 125 bool operator==( const stack_alloc<T>& lhs, const stack_alloc<T>& rhs) throw() 126 { 127 return lhs.get_stack() == rhs.get_stack(); 128 } 129 130 template <typename T> 131 bool operator!=( const stack_alloc<T>& lhs, const stack_alloc<T>& rhs) throw() 132 { 133 return lhs.get_stack() != rhs.get_stack(); 134 }