您的位置:首页 > 其它

STL源码剖析之开篇与内存配置器--学习笔记

2011-04-09 13:47 477 查看
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: