标签:C,C++,STL,源码,学习,笔记,内存,配置器
虽然STL的内存配置器在我们的实际应用中几乎不用涉及,但它却在STL的各种容器背后默默做了大量的工作,STL内存配置器为容器分配并管理内存。统一的内存管理使得STL库的可用性、可移植行、以及效率都有了很大的提升,因此STL内存配置器是STL库中最重要的组成部分之一。
1、Constructor和Destroy
我们所习惯的C++内存空间分配动作如下:
- class Foo { ... };
- Foo* pObj = new Foo;
- delete pObj;
delete操作符的动作也可以分为两部分:1. 调用对象的析构函数 (
alloc::deallocate 函数);2. 释放对象占用的内存空间(
alloc::destroy 函数)
在STL库的空间配置器中,new和delete的这两个过程都是分开的,这还要归功于C++语言的placement new操作符(具体请参考C++Primer)。
construct源码
- template <class _T1, class _T2>
- inline void _Construct(_T1* __p, const _T2& __value) {
- new ((void*) __p) _T1(__value); // 把初始值设定到指定的空间上,placement new操作符(参考C++ Primer)
- // 调用T1::T1(value)
- }
- template <class _T1>
- inline void _Construct(_T1* __p) {
- new ((void*) __p) _T1(); // 把初始值设定到指定的空间上,placement new操作符,调用T1::T1()
- }
- template <class _T1, class _T2>
- inline void construct(_T1* __p, const _T2& __value) { // construct的第一个版本
- _Construct(__p, __value);
- }
- template <class _T1>
- inline void construct(_T1* __p) { // construct的第二个版本
- _Construct(__p);
- }
- template <class _Tp>
- inline void _Destroy(_Tp* __pointer) {
- __pointer->~_Tp(); // 显式调用Tp::~Tp()
- }
- template <class _ForwardIterator>
- inline void _Destroy(_ForwardIterator __first, _ForwardIterator __last) {
- __destroy(__first, __last, value_type(__first));
- }
- // 以下这些基本数据类型在销毁时都不需要调用对象的析构函数
- inline void _Destroy(char*, char*) {} // char* 类型的特化
- inline void _Destroy(int*, int*) {} // int* 类型的特化
- inline void _Destroy(long*, long*) {} // long* 类型的特化
- inline void _Destroy(float*, float*) {} // float*类型的特化
- inline void _Destroy(double*, double*) {} // double*类型的特化
- #ifdef __STL_HAS_WCHAR_T
- inline void _Destroy(wchar_t*, wchar_t*) {} // wchar_t* 类型的特化
- #endif /* __STL_HAS_WCHAR_T */
- template <class _ForwardIterator, class _Tp> // 泛化
- inline void
- __destroy(_ForwardIterator __first, _ForwardIterator __last, _Tp*)
- {
- typedef typename __type_traits<_Tp>::has_trivial_destructor _Trivial_destructor;
- __destroy_aux(__first, __last, _Trivial_destructor()); // _Trivial_destructor() 会返回Tp类型的对象是否存在non-trivial destructor
- }
- template <class _ForwardIterator>
- void
- __destroy_aux(_ForwardIterator __first, _ForwardIterator __last, __false_type)
- {
- for ( ; __first != __last; ++__first) // 如果该类型的对象存在non-trivial destructor,则调用destroy的第一个版本来析构对象
- destroy(&*__first);
- }
- template <class _ForwardIterator>
- inline void __destroy_aux(_ForwardIterator, _ForwardIterator, __true_type) {} // 如果该类型的对象不存在non-trivial destructor,什么也不用做
- template <class _Tp>
- inline void destroy(_Tp* __pointer) { // destroy的第一个版本,析构单个Tp对象
- _Destroy(__pointer);
- }
- template <class _ForwardIterator>
- inline void destroy(_ForwardIterator __first, _ForwardIterator __last) { // destroy的第二个版本,析构first,last之间的所有Tp对象
- _Destroy(__first, __last);
- }
两个问题:
1. C++并不支持通过value_type来判断指针所指对象的类型,STL中通过什么来判断?
2. C++也不支持判断一个对象是否存在析构函数,STL如何来实现判断?
第一个问题参考头文件<stl_iterator.h>
- template <class interator>
- inline typename iterator_traits<interator>::value_type*
- value_type(const interator&)
- {
- return static_cast<typename iterator_triats<interator>::value_type*>(0);
- }
第二个问题请参看文章:《神奇的
__type_traits》
2、空间的分配与释放
SGI使用双层配置器,如果分配的空间大于128bytes则采用第一级配置器,直接调用malloc(),free();如果分配的空间小于128bytes则采用复杂一点的memory pool方式。在SGI中一级配置器的实现依靠的是模板类__malloc_alloc_template,二级配置器依靠模板类__default_alloc_template
SGI使用双层配置器,如果分配的空间大于128bytes则采用第一级配置器,直接调用malloc(),free();如果分配的空间小于128bytes则采用复杂一点的memory pool方式。在SGI中一级配置器的实现依靠的是模板类__malloc_alloc_template,二级配置器依靠模板类__default_alloc_template
SGI STL中的几种配置器
1、__malloc_alloc_template是一个基础配置器,直接调用malloc和free来分配和释放空间,如果空间不够会调用handler处理过后再重新分配。__malloc_alloc_template配置器被作为一级配置器来使用。
- template <int inst>
- class __malloc_alloc_template {
- private:
- static void *oom_malloc(size_t); // out of memory的情况下会调用此函数分配
- static void *oom_realloc(void *, size_t); // out of memory的情况下会调用此函数重新分配
- static void (* __malloc_alloc_oom_handler)(); // out of memory情况下会如果这个handler存在就会被调用
- public:
- static void * allocate(size_t n)
- {
- void *result = malloc(n); // 调用 普通的malloc函数直接分配
- if (0 == result) result = oom_malloc(n); // 如果分配失败才调用oom_malloc
- 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)
- {
- void * result = realloc(p, new_sz); // 调用普通的realloc函数重新分配
- if (0 == result) result = oom_realloc(p, new_sz); // 如果失败就调用oom_realloc函数重新分配
- return result;
- }
- static void (* set_malloc_handler(void (*f)()))() // 为out of memory的情况设置handler
- {
- void (* old)() = __malloc_alloc_oom_handler;
- __malloc_alloc_oom_handler = f;
- return(old);
- }
- };
- template <int inst>
- 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)(); // 如果存在handler就先调用handler回收能够回收的内存
- result = malloc(n); // 然后再尝试malloc
- if (result) return(result);
- }
- }
- template <int inst>
- void * __malloc_alloc_template<inst>::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)(); // 如果存在handler就先调用handler回收能够回收的内存
- result = realloc(p, n); // 然后再尝试realloc
- if (result) return(result);
- }
- }
- typedef __malloc_alloc_template<0> malloc_alloc; // STL的一级内存配置器
2.、__default_alloc_template也是一个基础配置器,和__malloc_alloc_template相比多了一下几个功能:
A. 多线程的支持,包括资源的加锁解锁
B. 分配空间的大小按8字节对齐
C. 如果分配的空间大于128字节,直接调用一级配置器的malloc_alloc::alloc函数来分配
D. 维护了16个自由空间的链表(free list),每个链表都链接了一串相等大小的自由空间,各个链表节点的自由空间大小不同,但都是8的倍数(8、16、24......96、104、112、120、128刚好16级)
E. 如果分配的空间小于128字节,找到一个链表,使得该链表节点的自由空间大小大于请求分配的空间而又与之大小最接近,从该链表中取出一个节点分配
F. 维护了一个memory pool,如果所有自由空间列表为空就会从记忆池中申请空间,记忆池尽量返回可用的空间。
__default_alloc_template 通常被作为二级配置器来使用,由于该类的代码非常繁杂,这里就不列出来了。有兴趣可以阅读STL的源代码。
- template <bool threads, int inst>
- class __default_alloc_template {
- ...
- };
- typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;
- typedef __default_alloc_template<false, 0> single_client_alloc;