STL源码剖析之开篇与内存配置器--学习笔记
2011-04-09 13:47 Aga.J 阅读(851) 评论(0) 编辑 收藏 举报1 从底层来看,STL带给我们一套具有实用价值的零部件,以及一个整合起来的整体,STL中组件之间耦合度很低,组件之间可以互相关联整合。
2 STL以泛型思维,描述了很多抽象概念,以抽象概念为主体而不是依赖于实际的类
3 STL六大组件:
容器 : 数据结构(容纳数据)是一种class template
算法 : 常用的算法,是一种function template
迭代器:容器和算法之间的胶合剂,也是一种泛型组件,“泛型指针”,每个容器都有自己的迭代器,只有容器自己才知道如何使用自己的迭代器来完成迭代。
仿函数:??
适配器:修饰容器或者仿函数或者迭代器接口的东西,也就是改变了被适配的东西,提供了新的接口。
配置器:空间配置和管理。
4 六大组件的关系
Container通过allocator取得数据的存储空间,Algorithm通过Iterator 存取 container的内容,functor协助algorithm完成不同策略的变化,adapter可以修饰functor
5 allocator的简单定义
#include<cstddef>
template<class T>
class allocator
{
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef size_t size_type;
//存在于C标准库的头文件stddef的C++版cstddef
//源类型是unsigned int,使用sizeof返回的类型就是size_t
//avoid specifying machine-dependent data size,提高代码可移植性
typedef ptrdiff_t difference_type;
//两个指针差的类型,因为不一定和int一样,于平台有关。
template<class U>
struct rebind
{ typedef allocator<U> other;};
pointer allocater(size_type n, const void*hint=0)
{
return _allocate( (difference_type)n, (pointer)0 );
{
template<class T>
inline T* _allocate(ptrdiff_t size,T*)
{
T* tmp=(T*) (::operator new((size_t)(size*sizeof(T))) ):
if (tmp==0)
exit(1);
return tmp;
}
//泛型思想,为特定大小的区间分配内存,返回指针
//使用ptrdiff_t来得到独立于平台的地址差值
}
}
void deallocate(pointer p, size_type n)
{
_deallocate(p);
{
template<class T>
inline void _deallocate(T* buffer)
{
::operator delete(buffer);
}
//泛型思想,operator delete
}
}
void construct(pointer p, const T& value)
{
_construct(p,value);
{
template<class T1,class T2>
inline void _construct(T1* p,const T2& value)
{
new(p) T1(value);
}
}
}
void destroy(pointer p)
{
_destroy(p);
{
template<class T>
inline void _destroy(T* ptr)
{
ptr->~T();
}
}
}
pointer address(reference x)
{
return (pointer)&x; //返回对象的地址(指针)
}
const_pointer const_address(const_reference x);
{
return (const_pointer)&x;
}
size_type max_size() const
{
return size_type( UINT_MAX / sizeof(T) ); //可成功配置的最大
}
};
// vector<int, allocator<int> > iv(ia,ia+5);
// 底层上使用allocator来分配适当的空间,来存取数据等
6 SGI STL中的配置器是这样使用的std::alloc,而我们通常会使用缺省的空间配置器,所以SGI STL为每个容器都提供了默认参数说明空间配置器
Template<class T, class Alloc = alloc>
Class vector{};
7 [5]中所定义的allocator只是基层内存配置和释放(::operator new和::operator delete)的一层薄薄的包装,并没有考虑到效率上的优化。是SGI所定义的一个符合部分标准的配置器,【6】中所说的是正式使用的alloc,是特殊的空间配置器,内部使用
8 配置器定义在memory文件中,而该文件包含了stl_alloc,stl_construct(一个完成内存分配,一个完成构造)
9 stl_construct的实现(内存配置前对象的构造行为)
<stl_construct.h>
#include<new.h>
template<class T1,class T2>
inline void construct(T1*p,const T2& value)
{
new(p) T1(value); // placement new 调用T1::T1(value)
}
template<class T>
inline void destroy(T* pointer) //接受单个指针,直接调用析构函数
{
pointer->~T();
}
template<class ForwardIterator>
inline void destroy(ForwardIterator first,ForwardIterator last)
//接受两个迭代器,删除区间对象
{
__destroy(first,last ,value_type(first);
//但是如果每个对象的析构函数都是trivial即无关痛痒的,就没必要一次一次调用析构
//所以这里使用value_type来获得对象的类型,再利用__type_trait<T>来判断是不是无关痛痒的
}
template<class ForwardIterator,class T>
inline void __destroy(ForwardIterator first,ForwardIterator last,T*)
{
typedef typename __type_trait<T>::has_trivial_destructor trivial_destructor;
__destroy_aux(first,last,trivial_destructor());
//根据传入的对象类型,使用tribial_destructor来判断是不是无关痛痒的
}
template<class ForwardIterator>
inline void __destroy_aux(ForwardIterator first,ForwardIterator last,__false_type)
{
for(;first<last;++first)
destroy(&*first);
}
template<class ForwardIterator>
inline void __destroy_aux(ForwardIterator,ForwardIterator,__true_type)
{
}
inline void destroy(char*,char*){}
inline void destroy(wchar_t*,wchar_t*){}
这是一种考虑到效率的construct和destroy!!!!!!
10 前面是内存配置后的 对象构造 和 内存释放前的 对象析构,下面是具体的内存的配置和释放。
对象构造前的空间配置和对象构造后的空间释放由<stl_alloc.h>负责。
在空间的配置和释放时要考虑:1 堆空间申请 2 多线程情况 3 内存不足时 4 内存碎片
C++的内存配置基本操作是::operator new() ::operator delete()这是两个“全局函数”,相当于C的malloc和free。
11 因为考虑到内存碎片的情况,所以SGI采用【双层级配置器】,第一级配置器直接使用malloc和free。而第二级则: 当 申请区超过128 byte 时,调用一级配置器。当小于128时,采用复杂的memory pool(内存池)
#ifdef __USE_MALLOC
….
Typedef __malloc_alloc_template<0> malloc_alloc;
Typedef malloc_alloc alloc; // 令alloc为第一级配置器
#else
Typedef __default_alloc_template<__NODE_ALLOCATOR_THREAD,0> alloc;
// 令alloc为二级配置器
#endif
也就是说malloc alloc template就是第一级配置器,default alloc template就是第二级配置器。
Template<int inst>
Class __malloc_alloc_template
{
//allocate使用malloc
//deallocate使用free
//模拟C++的setNewHandler处理内存不足的情况
};
Template<bool threads,int inst>
Class __default_alloc_template
{
//如果需求区大于128byte,就转用一级配置器
//否则二级要防止碎片发生
// 维护16个 free list
// 负责16种小块的次配置能力
// 内存池用alloc配置得到
};
然后再定义配置器功能接口
Template<class T, class Alloc>
Class simple_alloc
{
Public:
Static T *allocate(size_t n) {return 0== n? 0:(T*)Alloc::allocate(n*sizeof(T)) ;}
Static T *allocate(void) {return (T*) Alloc::allocate(sizeof(T));}
Static void deallocate(T* p,size_t n) {if (0!=n) Alloc::deallocate(p,n*sizeof(T));}
Static void deallocate(T *p){ Alloc::deallocate(p,sizeof(T));}
//将调用传递给配置器的成员函数,可能是第一级也可能是第二级
};
SGI的STL容器全部使用这个simple_alloc接口
Template<class T, class Alloc=alloc> //缺省使用alloc为配置器
Class vector
{
Protected:
Typedef simple_alloc<value_type,Alloc> data_allocator;
//使用这个simple_alloc就可以隐藏底层的byte字节参数的传入,使用类型即可
Void deallocate()
{
If()
Data_allocator::deallocate( start, end_of_storage-start);
//start是value type,第二个参数则是指针差
}
};
首先利用条件编译决定是使用二级配置器还是一级配置器,并且使用typedef来统一配置器的命名为alloc(默认情况下所有容器使用该配置器)
然后为每个容器加上该配置器(配置器也作为模板参数,但是默认情况下是预定义的配置器)
因为默认配置器内的allocate定义等,需要传入byte大小,所以封装这些复杂的输入,抽取出一个simple_alloc的类,来完成基本的byte转换。
最后就在每个容器中定义这个simple_alloc类,完成初始化分配;
以上是一个比较完整的一级二级配置器的设计结构,暂时不涉及底层的实际分配情况。
下面介绍实际的__malloc_alloc_template和__default_alloca_template
12 所以最底层的实现是上面两个一级和二级配置器的实现,simple_alloc只是一个高层接口,而容器则是通过这样的配置器来完成内存空间的分配。
一级配置器 malloc_alloc_allocator 比default那个速度慢,但是空间利用率高,而且是thread safe
Template<int inst>
Class __malloc_alloc_template //一级配置器
{
Private:
Static void *oom_malloc(size_t);
Static void *oom_realloc(void*,size_t);
Static void (* __malloc_alloc_oom_handler)();
Public:
Static void *allocate(size_t n) //实际的allocate函数(simple_alloc里面定义的)
{
Void *result = malloc(n); //实际上是调用C的malloc来实现,一旦分配
//失败,则使用oom_malloc
If( 0 == result) result=oom_malloc(n); //无法满足需求时,使用oom_malloc
Return result;
}
Static void deallocate(void *p,size_t )
{
Free(P); //直接使用C的free
}
Static void *reallocate(void *p, size_t oldSize, size_t new_sz)
{
Void *result = realloc(p,new_sz); //还是调用底层的realloc
If (0==result) result=oom_realloc(p,new_sz);
Return result;
}
Static void (* set_malloc_handler(void (*f) () ) ) ()
{
Void (*old)() = __malloc_alloc_oom_handler; //转换为void指针
__malloc_alloc_oom_handle=f; //然后指向f
Return (old); //返回这个指针
}
//模仿C++的set_new_handler,我们可以自己指定out of memory handler
};
Template<int inst>
Void (*__malloc_alloc_template<inst>::__malloc_alloc_com_handler ) () =0;
Template<int inst>
Void *__malloc_alloc_template<inst>::oom_malloc(size_t n) //oom_malloc的实现
{
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); //result = realloc(p,n); //并且重新分配,知道分配成功
If( result) return (result);
}
}
//因为不是使用C++的::operator new,所以不能直接使用C++的new handler只能仿真
//在该(分配失败处理)中,不断调用“内存不足处理例程”,期望在某次调用后,可以获得内存完成任务,而如果“该例程么有被客户端设置,则直接抛出错误”。
Typedef __malloc_alloc_template<0> malloc_alloc;
13 二级配置器 __default_alloc_template
二级配置器能帮助我们避免太多小额区块所造成的内存碎片,但是小额碎片在配置时会有标志区域(这会带来额外的负担),所以小额区域越小,负担越大。(既需要一个pa,又需要使用cookie来记录内存的大小)
二级配置器的做法是:如果块够大,超过128byte时,则移交第一级配置器处理,当块小于128时,则以内存池(memory pool)管理:每次配置一大块内存,并维护所对应的free list,如果下次有相同大小的请求,则直接从free list中取出。如果释放了 ,则回收到free list中。(为了配置方便,二级配置器会将小额内存需求量上调至8的倍数)然后维护16个free list,每个list管理 8,16。Xxxx的区域大小
一种free list的定义可能是这样
Struct Node
{
Int size;
Char client_data[];
Node* next;
};
这样一来,每个节点都需要一个额外的指针来指向下一个节点,这会带来新的负担。所以使用下面的定义方式
Union obj
{
Union obj *free_list_link;
Char client_data[];
};
利用union的好处,第一个字段可以指向另一个obj,第二个字段可以指向 实际区块。(怎样做到节省了内存?)
哦,也就是说,元素的free—list只保存了下一个节点的地址,也就是使用了第一个obj,然后再被申请后,就会使用第二个字段(这时候也没必要维护它在free中的位置),所以很巧妙。
实现:
Enum {__ALIGN = 8};
Enum { __MAX_BYTES = 128};
Enum { __NFREELISTS = __MAX_BYTES/__ALIGN};
Template<bool threads, int inst>
Class __default_alloc_template
{
Private:
Static size_t ROUND_UP(size_t bytes) //将byte上调到8的倍数
{ return (bytes)+8 – byte+8 % 8}
{ return ( ((bytes)+__ALIGN-1) & ~(__ALIGN -1 ) ) ; }
//思想:加上8后,进一步对齐,然后再取出最后3位,保证没有多余的数,这样就可以整除8了
Private:
Union obj
{
Union obj *free_list_link;
Char client_data[1];
};
Private:
Static obj *volatile free_list[__NFREELISTS];
Static size_t FREELIST_INDEX (size_t bytes) //根据所需内存块的大小,决定使用
//第n号的free-list
{ return (((bytes)+__ALIGN -1 )/ __ALIGN -1 ); }
Static void *refill (size_t n); //加入free list
Static char *chunk_alloc(size_t size, int &nobjs); //配置一个可以容纳多个size的区域
Static char *start_free; //内存池起始位置
Static char *end_free;
Static size_t heap_size;
Public:
Static void *allocate( size_t n) {}
Static void deallocate ( void *p, size_t n){}
Static void *reallocate( void *p, size_old_sz, size_t new_sz):
};
Template<bool threads,int inst>
Char *__default_alloc_template<threads, inst>::start_free=0;
Template<bool threads,int inst>
Char *__default_alloc_template<threads,inst>::end_free=0;
Template<bool threads,int inst>
Size_t __default_alloc_template<threads,inst>::heap_size = 0;
Template<bool threads, int inst>
__defualt_alloc_template<threads,inst>::obj *volatile __default_alloc_template<threads,inst>::free_list __NFRESSLISTS ={0};
//二级配置器,使用内存池的概念,主要是维护一个free list列表,每个free list都对应一种大小的空闲区域块。(辅助函数是上调块大小,返回对应free list号),然后主要完成allocate,deallocate,reallocate(这些函数再调用私有分配函数完成)
做为配置器,__default_alloc_template拥有配置器的标准接口函数allocate(这就是为什么定义一个新的分配接口) 当调用allocate时,先判断块的大小,如果大于128就使用第一级的配置器,小于128就根据实际byte大小检查对应的free list,如果有可用的块,就拿来使用,如果没有的话,就上调至8倍数的边界,然后refill,
Static void *allocate(size_t n)
{
Obj *volatile *my_free_list;
Obj *result;
If ( n> (size_t)__MAX_BYTES)
{
Rerurn (malloc_alloc::allocate(n)); //调用一级配置器
}
My_free_list = free_list + FREELIST_INDEX(n); //指向数组的一个slot
Result = my_free_list;
If( result == 0) //没有可用的
{
Void *r= refill( ROUND_UP(n)); //没有可用的如何理解,refill如何理解
Return r;
}
*my_free_list = result->free_list_link; //指向下一个未分配的node
Return result;
}
Static void deallocate( void *p,size_t n)
{
Obj *q=(obj*)p;
Obj * volatile *my_free_list;
If ( n>(size_t)__MAX_BYTES)
{
Malloc_alloc::deallocate(p,n);
Return;
}
My_free_list = free_list + FREELIST_INDEX(n);
q->free_list_link = *my_free_list;
*my_free_list=q;
//q里面的脏数据怎么解决???delete []q.data;
}
前面allocate时,如果free list没有可用的块时,则使用refill,即为free list重新填充空间。新的空间有chunk_alloc完成
Template<bool threads, int inst>
Void* __default_alloc_template<threads,inst>::refill(size_t n)
{
Int nobjs=20;
Char *chunk = chunk_alloc( n,nobjs); //分配20个新节点
Obj *volatile *my_free_list; Obj *result; Obj *current_obj, *next_obj; Int I;
If (1==nobjs); return (chunk); //仅仅获取一个,就给调用者使用
My_free_list = free_list+ FREELIST_INDEX(n);
Result = (obj*) chunk; //第一块准备返回给客户
*my_free_list = next_obj =(obj*)(chunk+n); //free指向新配置的空间
For (i=1;;i++) //从第一个节点开始,连接上所有节点
{
Current_obj = next_obj;
Next_obj =(obj*) ( (char*)next_obj +n); //字节上操作
If (nobjs -1 == i)
{
Current_obj -> free_list_link=0;
Break;
}
Else
{
Current_obj -> free_list_link = next_obj;
}
}
Return (result);
}
内存池 memory pool chunk_alloc的源码
Template<bool threads, int inst>
Char *__default_alloc_template<threads,inst>:: chunk_alloc( size_t size, int& nobjs)
{
Char *result;
Size_t total_bytes = size * nobjs;
Size_t bytes_left = end_free – start_free; //内存池的剩余空间
If ( bytes_left >= total_bytes)
{
Result = start_free;
Start_free += total_bytes;
Return (result);
}
Else if ( bytes_left >=size)
{
Nobjs = bytes_left/size;
Total_bytes=size*nobjs;
Result= start_free;
Start_free+=total_bytes;
Return result;
}
Else
{
….
}
}
内存池的操作:
1 计算 内存池的剩余空间, 计算 所需要的总数空间
2 如果 空间满足,则返回其实位置,并重新设定剩余空间大小
3 如果不能满足,但是足够提供一个以上的区块,则给出这么多个区块的空间
4 如果一个区块都不能满足,??
14 内存基本处理工具
STL定义了5个全局的函数来操作 已经分配但是未初始化 的空间。
Construct,destroy,uninitialized_copy, uninitialized_fill, uninitialized_fill_n
1) uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result)
如果目的地 result,result+(last-first) 未初始化,那么这个函数会使用 复制构造函数为first到last之间的对象产生一个copy [ construct ( & * (result +(i-first)), *i ) ]
2) uninitialized_fill ( ForwardIterator first, ForwardIterator last, const T& x);
如果first到last指向的目标区域都指向没有初始化的内存,那么该函数会产生x类型的复制品。
3) uninitailized_fill_n
作者:Aga.J
出处:http://www.cnblogs.com/aga-j
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
个人学习笔记仅供本人记录知识所用,不属发表性文章。