1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
template <class _Tp, class _Alloc> class _Vector_base { public: typedef _Alloc allocator_type; allocator_type get_allocator() const { return allocator_type(); } _Vector_base(const _Alloc&) : _M_start(0), _M_finish(0), _M_end_of_storage(0) {} _Vector_base(size_t __n, const _Alloc&) : _M_start(0), _M_finish(0), _M_end_of_storage(0) { _M_start = _M_allocate(__n); _M_finish = _M_start; _M_end_of_storage = _M_start + __n; } ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); } protected: _Tp* _M_start; _Tp* _M_finish; _Tp* _M_end_of_storage; typedef simple_alloc<_Tp, _Alloc> _M_data_allocator; _Tp* _M_allocate(size_t __n) { return _M_data_allocator::allocate(__n); } void _M_deallocate(_Tp* __p, size_t __n) { _M_data_allocator::deallocate(__p, __n); } }; |
C++的STL中定义了很多容器,容器的第二个模板参数通常为allocator类型,用于帮助将内存分配和对象的构造分离开来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
# define __NODE_ALLOCATOR_THREADS true //SGI专用 # ifdef __STL_SGI_THREADS // We test whether threads are in use before locking. // Perhaps this should be moved into stl_threads.h, but that // probably makes it harder to avoid the procedure call when // it isn't needed. extern "C" { extern int __us_rsthread_malloc; } // The above is copied from malloc.h. Including <malloc.h> // would be cleaner but fails with certain levels of standard // conformance. # define __NODE_ALLOCATOR_LOCK if (threads && __us_rsthread_malloc) \ { _S_node_allocator_lock._M_acquire_lock(); } # define __NODE_ALLOCATOR_UNLOCK if (threads && __us_rsthread_malloc) \ { _S_node_allocator_lock._M_release_lock(); } # else /* !__STL_SGI_THREADS */ # define __NODE_ALLOCATOR_LOCK \ { if (threads) _S_node_allocator_lock._M_acquire_lock(); } # define __NODE_ALLOCATOR_UNLOCK \ { if (threads) _S_node_allocator_lock._M_release_lock(); } # endif #else // Thread-unsafe # define __NODE_ALLOCATOR_LOCK # define __NODE_ALLOCATOR_UNLOCK # define __NODE_ALLOCATOR_THREADS false #endif __STL_BEGIN_NAMESPACE #if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) #pragma set woff 1174 #endif |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
// Malloc-based allocator. Typically slower than default alloc below. // Typically thread-safe and more storage efficient. #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG # ifdef __DECLARE_GLOBALS_HERE void (* __malloc_alloc_oom_handler)() = 0; // g++ 2.7.2 does not handle static template data members. # else extern void (* __malloc_alloc_oom_handler)(); # endif #endif //第一级配置器,_inst没有用 template <int _inst> class __malloc_alloc_template { private: //oom是指out of memory,定义函数指针,用来处理内存申请失败的情况 static void* _S_oom_malloc(size_t);//C++ set_new_handler() 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); //第一级配置器直接用malloc申请内存 //当malloc申请失败,使用_S_oom_malloc> if (0 == __result) __result = _S_oom_malloc(__n); return __result; } static void deallocate(void* __p, size_t /* __n */){ free(__p); // 第一级配置器直接用free释放内存 } static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz){ // 第一级配置器直接使用 realloc() void* __result = realloc(__p, __new_sz); if (0 == __result) __result = _S_oom_realloc(__p, __new_sz); return __result; } /* 设置内存申请失败的错误处理函数,类似 C++ 的 set_new_handler()。 这个是客端设置的,而不是编译器设置。如果不设置,则内存配置失败马上终止 */ static void (* __set_malloc_handler(void (*__f)()))(){ void (* __old)() = __malloc_alloc_oom_handler; __malloc_alloc_oom_handler = __f; return(__old); } //这里应该就是在设置自己的错误处理函数 }; // malloc_alloc out-of-memory handling,初始值为0 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG template <int __inst> void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0; #endif 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; //如果没有设置out-of-memory handling处理函数,则抛出异常,终止 if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; } (*__my_malloc_handler)(); //调用处理函数,企图释放内存 __result = malloc(__n); //再次尝试配置内存 if (__result) return(__result); } } template <int __inst> void* __malloc_alloc_template<__inst>::_S_oom_realloc(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; } (*__my_malloc_handler)(); __result = realloc(__p, __n); if (__result) return(__result); } } typedef __malloc_alloc_template<0> malloc_alloc; |
补充知识:当运算符new找不到足够大的连续内存块来为对象分配内存时将会发生什么?一个称为 new-handler的函数被调用。对于new-handler的缺省动作是抛出一个异常。然而,如果我们在程序里用堆分配,至少要用“内存已用完”的信息代替 new-handler,并异常中断程序。在调试程序时会得到程序出错的线索。
1 2 3 4 5 6 7 8 9 10 11 |
namespace std { #ifdef _M_CEE_PURE typedef void (__clrcall * new_handler) (); #else typedef void (__cdecl * new_handler) (); #endif #ifdef _M_CEE typedef void (__clrcall * _new_handler_m) (); #endif _CRTIMP2 new_handler __cdecl set_new_handler(_In_opt_ new_handler _NewHandler) throw(); }; |
new_handler是一个自定义的函数指针类型,它指向一个没有输入参数也没有返回值的函数;set_new_handler则是一个输入并返回new_handler类型的函数。set_new_handler的输入参数是operator new分配内存失败时要调用的出错处理函数的指针,返回值是set_new_handler没调用之前就已经在起作用的旧的出错处理函数的指针。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include<new.h> #include<stdlib.h> #include <iostream> using namespace std; void __cdecl newhandler(){ cout << "The new_handler is called:" << endl; throw bad_alloc(); return; } int _tmain(int argc, _TCHAR* argv[]){ set_new_handler (newhandler); try{ while (1){ new int[5000000]; cout << "Allocating 5000000 ints." << endl; } } catch ( exception e ){ cout << e.what() << " xxx" << endl; } return 0; } |
1 2 3 4 5 6 7 8 9 10 11 |
int __cdecl memory(size_t ){ cout<<"内存耗尽!"<<endl; exit(0); } int _tmain(int argc, _TCHAR* argv[]){ ::_set_new_handler(memory); while ( 1 ){ new int[5000000]; cout << "Allocating 5000000 ints." << endl; } |
设计一个良好的new_handler函数必须做以下事情:
产生更多的可用内存: 这将使operator new下一次分配内存的尝试有可能获得成功。实施这一策略的一个方法是:在程序启动时分配一个大的内存块,然后在第一次调用new-handler时释 放。释放时伴随着一些对用户的警告信息,如内存数量太少,下次请求可能会失败,除非又有更多的可用空间。
安装另一个不同的new-handler函数: 如果当前的new-handler函数不能产生更多的可用内存,可能它会知道另一个new-handler函数可以提供更多的资源。这样的话,当前的 new-handler可以安装另一个new-handler来取代它(通过调用set_new_handler)。下一次operator new调用new-handler时,会使用最近安装的那个。(这一策略的另一个变通办法是让new-handler可以改变它自己的运行行为,那么下次 调用时,它将做不同的事。方法是使new-handler可以修改那些影响它自身行为的静态或全局数据。
卸除new-handler:也就是传递空指针给set_new_handler。没有安装new-handler,operator new分配内存不成功时就会抛出一个标准的std::bad_alloc类型的异常。
抛出std::bad_alloc或从std::bad_alloc继承的其他类型的异常: 这样的异常不会被operator new捕捉,所以它们会被送到最初进行内存请求的地方。(抛出别的不同类型的异常会违反operator new异常规范。规范中的缺省行为是调用abort,所以new-handler要抛出一个异常时,一定要确信它是从std::bad_alloc继承来 的。
没有返回:典型做法是调用abort或exit。abort/exit可以在标准c库中找到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
/* 无论alloc是第一级配置器还是第二级配置器,SGI还为它包装一个接口,使之 符合STL规范 */ 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)); } }; // Allocator adaptor to check size arguments for debugging. // Reports errors using assert. Checking can be disabled with // NDEBUG, but it's far better to just use the underlying allocator // instead when no checking is desired. // There is some evidence that this can confuse Purify. template <class _Alloc> class debug_alloc { private: enum {_S_extra = 8}; // Size of space used to store size. Note // that this must be large enough to preserve // alignment. public: static void* allocate(size_t __n) { char* __result = (char*)_Alloc::allocate(__n + (int) _S_extra); *(size_t*)__result = __n; return __result + (int) _S_extra; } static void deallocate(void* __p, size_t __n) { char* __real_p = (char*)__p - (int) _S_extra; assert(*(size_t*)__real_p == __n); _Alloc::deallocate(__real_p, __n + (int) _S_extra); } static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz) { char* __real_p = (char*)__p - (int) _S_extra; assert(*(size_t*)__real_p == __old_sz); char* __result = (char*) _Alloc::reallocate(__real_p, __old_sz + (int) _S_extra, __new_sz + (int) _S_extra); *(size_t*)__result = __new_sz; return __result + (int) _S_extra; } }; # ifdef __USE_MALLOC typedef malloc_alloc alloc; typedef malloc_alloc single_client_alloc; # else // Default node allocator. // With a reasonable compiler, this should be roughly as fast as the // original STL class-specific allocators, but with less fragmentation. // Default_alloc_template parameters are experimental and MAY // DISAPPEAR in the future. Clients should just use alloc for now. // // Important implementation properties: // 1. If the client request an object of size > _MAX_BYTES, the resulting // object will be obtained directly from malloc. // 2. In all other cases, we allocate an object of size exactly // _S_round_up(requested_size). Thus the client has enough size // information that we can return the object to the proper free list // without permanently losing part of the object. // // The first template parameter specifies whether more than one thread // may use this allocator. It is safe to allocate an object from // one instance of a default_alloc and deallocate it with another // one. This effectively transfers its ownership to the second one. // This may have undesirable effects on reference locality. // The second parameter is unreferenced and serves only to allow the // creation of multiple default_alloc instances. // Node that containers built on different allocator instances have // different types, limiting the utility of this approach. #if defined(__SUNPRO_CC) || defined(__GNUC__) // breaks if we make these template class members: enum {_ALIGN = 8}; enum {_MAX_BYTES = 128}; enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN #endif 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 } } static void* reallocate(void* __p, size_t __old_sz, size_t __new_sz); } ; typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc; typedef __default_alloc_template<false, 0> single_client_alloc; template <bool __threads, int __inst> inline bool operator==(const __default_alloc_template<__threads, __inst>&, const __default_alloc_template<__threads, __inst>&) { return true; } # ifdef __STL_FUNCTION_TMPL_PARTIAL_ORDER template <bool __threads, int __inst> inline bool operator!=(const __default_alloc_template<__threads, __inst>&, const __default_alloc_template<__threads, __inst>&) { return false; } # endif /* __STL_FUNCTION_TMPL_PARTIAL_ORDER */ |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
#ifdef __STL_USE_STD_ALLOCATORS template <class _Tp> class allocator { typedef alloc _Alloc; // The underlying allocator. public: typedef size_t size_type; typedef ptrdiff_t difference_type; typedef _Tp* pointer; typedef const _Tp* const_pointer; typedef _Tp& reference; typedef const _Tp& const_reference; typedef _Tp value_type; //一个嵌套的class template,class rebind 拥有唯一成员other template <class _Tp1> struct rebind { typedef allocator<_Tp1> other; }; allocator() __STL_NOTHROW {} allocator(const allocator&) __STL_NOTHROW {} template <class _Tp1> allocator(const allocator<_Tp1>&) __STL_NOTHROW {} ~allocator() __STL_NOTHROW {} //返回某个对象的地址 pointer address(reference __x) const { return &__x; } //返回某个const对象的地址 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. //配置空间,足以存储n个对象 _Tp* allocate(size_type __n, const void* = 0) { return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp))) : 0; } // __p is not permitted to be a null pointer. //归还先前配置的空间 void deallocate(pointer __p, size_type __n) { _Alloc::deallocate(__p, __n * sizeof(_Tp)); } size_type max_size() const __STL_NOTHROW { return size_t(-1) / sizeof(_Tp); } void construct(pointer __p, const _Tp& __val) { new(__p) _Tp(__val); } void destroy(pointer __p) { __p->~_Tp(); } }; |