***上一篇是对STL空间配置器的入门级理解,在这一篇中,我将讨论更加深入的SGI STL空间适配器的内容。在下一节中,我将根据自己的理解,结合STL标准接口,实现一个符合STL标准的具有次级配置能力的简单空间配置器,将剪掉一切不需要的代码,在加上我自己的理解,实现一个更容易阅读与理解的空间配置器。
在开始进入正题之前,我先来谈谈为什么要花这么长的时间在看空间配置器的部分,而且对于学习如何使用STL来说,根本就不需要知道有空间适配器的存在,因为我们在实际编程中是不需要与空间配置器打交道的。那我想说,如果仅仅是为了学习如何使用STL去编程,那大可不必去阅读他的源代码,只需要记住每个函数的功能和用法,然后就可以解决问题。但那不是学会STL,那是学会用STL。这是最为容易做到的事情,我相信只要有点编程语言基础的人,给上一个小时就完全可以用STL的各种组建来做事情了。***
但是这远远不够,学习STL的核心在于理解他的设计原理,明白了原理,才能更好的去用STL,比如,如果你不知道STL容器之间的区别,那我相信大多数人在哪都会用一个容器vector,这个容器已然成了“通用容器”,确实,对于一般性问题,vector足够强大去承担各种场景,但是,你想想,如果只需要vector就能把事情做好,那为什么STL还有list呢?为什么还有queue呢?那map呢?思考从这里开始,但是对于本节来讲,这些思考到这里结束。
我们现在来讨论SGI STL的空间配置器,这是STL能正常、合理、高效工作的基础和动力,他让使用STL的各个组件变得更加容易,有了空间配置器,我们就不需要自己去管理内存,当然也不需要自己去处理内存不足的问题。既然是STL的基础,那么就要打好这个基础,STL从这里开始,我想,最后还是会回到这里。
STL空间配置器有一套标准接口,这对我们很重要,因为如果我们想要自己写一套属于自己的STL版本,那就应该努力符合STL标准。SGI当然也有一个符合STL标准的空间配置器,但是在源代码的开始,SGI就奉劝大家不要轻易使用这个版本的空间配置器,留着它只是为了兼容STL标准,所以,很明显这个文件不会被SGI STL用到,但是我们还是来看看它吧,也乘机了解了解STL标准的空间配置器张什么样子。
//为了兼容STL标准而写的这个文件,实际上SGI STL并没有使用这个文件作为他的空间配置器
//下面这个类提供标准STL申请对象函数allocate
template <class T>
inline T* allocate(ptrdiff_t size, T*) {
set_new_handler(0); //应该还记得new-handler机制吧(out-of-memory)
T* tmp = (T*)(::operator new((size_t)(size * sizeof(T))));
if (tmp == 0) {
cerr << "out of memory" << endl;
exit(1);
}
return tmp; //返回一个对象指针
}
//空间配置器析构函数
template <class T>
inline void deallocate(T* buffer) {
::operator delete(buffer);
}
//标准SGI框架配置器
template <class T>
class allocator {
public:
//一组别名定义,不明真相的我以为这很没有必要,可以直接将T改名为Type就ok啦
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;
typedef ptrdiff_t difference_type;
//配置n个空的对象
pointer allocate(size_type n) {
return ::allocate((difference_type)n, (pointer)0);
}
//释放
void deallocate(pointer p) { ::deallocate(p); }
//取地址
pointer address(reference x) { return (pointer)&x; }
const_pointer const_address(const_reference x) {
return (const_pointer)&x;
}
//返回可成功配置的最大对象数量
size_type init_page_size() {
return max(size_type(1), size_type(4096/sizeof(T)));
}
size_type max_size() const {
return max(size_type(1), size_type(UINT_MAX/sizeof(T)));
}
};
//ok,在你的需要配置的地方,使用这个就可以了
class allocator<void> {
public:
typedef void* pointer;
};
看了上面的代码,我们应该知道了标准STL空间配置器应该含有哪些接口了吧?我将在下一节中实现一个标准空间配置器。
SGI真正使用的配置器的设计非常巧妙,它实现了一级配置器和次级配置器,两级配置器相互配合,高效的为STL各种组建提供动力。我们已经在上一节中介绍了两级配置器的原理,还介绍了它们是如何协调工作的,所以在这里我也不会再次花费篇幅来讲这两级配置器,但是,我想讲讲内存管理。
当我们听到内存管理的时候,一般会想到操作系统,内核,MMU这些东西,确实,在我们的应用中,特别是在C++语言中,我们自己管理内存较为简单,new和delete配合使用即可,但即便如此,也免不了一些内存泄漏的BUG,作为一个高效安全的c++标准库,STL用空间适配器将STL的组建的内存管理得井然有序,不需要我们自己花费精力去管理。那什么是内存管理呢?本质是什么呢?我以为,内存管理其实就是管理地址空间,内存的申请与释放相当于对某一地址空间的上标记与解标记,上标记则表明已经被分配,用不用是程序做的事情,反正这段地址上了标记就是表明不能被再次分配了。有人可能会说我不是在说废话吗?内存不就是地址吗?其实,我必不反对将内存说成地址空间,因为我就是那么去理解内存的,进一步,对于32位的地址线,最大内存是2^32=4Gb,所以从内存是怎么得到的来看,确实,内存就是地址空间,能提供的地址空间越大,那能管理的内存就越多,64位的地址线能管理的内存肯定大于32位的。
但是,内存不是地址,我只能说这么多!
对于STL里面的空间适配器,它使用了malloc和free函数来实现内存的分配与回收,没有使用我们熟悉的c++里面的new和delete,很明显,malloc和free的效率高于new和delete,但有可能对于程序员来说,new和delete更为熟悉和安全。
SGI STL空间适配器用到了一种称为“内存池”的技术,这是一种较为高级的内存管理技术,在实现上,可以首先向操作系统申请一块较大的内存空间,然后以后的每次内存操作都使用内存池接口,这样就可以实现用内存池来管理程序中的内存操作,如果设计合理,使用内存池可以提高代码的效率,还能有效减少内存碎片化问题,比直接使用malloc和free好很多。
在下一节中,我将实现一个通用的内存池,在这之前,还是先看看SGI空间配置器的内存池是如何设计的吧。(在我自己实现的空间配置器里面,我将使用内存池技术,所以到时候再一次详谈)。
最后,我们来看看SGI STL的空间配置器的文件组织模式。
下面是在文件memory里面发现的头文件:
#include <stl_algobase.h>
#include <stl_alloc.h> //负责内存空间的配置与释放,里面有一二级配置器
#include <stl_construct.h> //负责对象内容的构造与析构
#include <stl_tempbuf.h>
#include <stl_uninitialized.h> //全局函数,辅助
#include <stl_raw_storage_iter.h>
所以,我们主要关注三个文件,一个是stl_alloc.h,这是SGI使用的空间配置器的文件,stl_construst.h文件里面定义了construct和destory函数用来操作对象,stl_uninitialized.h文件则包含两个主要的全局内存操作函数,复制和填充内存。
因为目前还在配置器阶段,所以我们将使用SGI的这些函数来为我们工作,等时机成熟,我们将改写这些函数。
好啦,下面我们就要开始真正的写一个自己的配置器了,主要内容依然参照SGI STL的空间配置器,但是去掉了一切高级的功能(比如线程支持),这些支持将在最后加上。
/* * CopyRight (c) 2016 * HuJian in nankai edu. * * Permission to use, copy, modify, distribute and sell this software * and its documentation for any purpose is hereby granted without fee, * provided that the above copyright notice appear in all copies and * that both that copyright notice and this permission notice appear * in supporting documentation. Silicon Graphics makes no * representations about the suitability of this software for any * purpose. It is provided "as is" without express or implied warranty. * * Time :2016/4/5 in nankai edu */
#ifndef _HJ_STL_ALLOC_H_
#define _HJ_STL_ALLOC_H_
#include<cstdio>
#include<climits>
#include<iostream>
#include<cstddef>
//if out of memory,just exit after print the info.
#define _THROW_BAD_ALLOC cerr<<"out of memory"<<endl; exit(1);
//this is the new-handler handler,using when oom
void(*_malloc_alloc_oom_handler_hjstl)() = 0;
//this is the first class of mmu in hjstl,this class will work
//if the second dispatch the job to it.
template<int inst>
class _malloc_alloc_first{
private:
static void* oom_malloc(size_t); //this function use to malloc and has the new-handler
static void* oom_remalloc(void*, size_t);//re-malloc
public:
//this function is the first allocator of hjstl
static void* allocate(size_t size)
{
void* result = malloc(size);
//check and dispatch it to oom_malloc if oom
if (0 == result) result = oom_malloc(size);
return result;
}
//re-malloc,same as malloc
static void* remallocate(void* old, size_t/*old mem size*/, size_t new_size)
{
void* result = realloc(old, new_size);
if (0 == result) result = oom_remalloc(old, new_size);
return result;
}
//de-allocate
static void deallocate(void* mem, size_t size)
{
//we not use the memory pool,so just use free
//but we will use the memory pool to manage the memory of
//hjstl later
free(mem);
}
//we can use this function to set the new handler
static void(*set_malloc_handler(void(*handler)()))()
{
//get the old oom handler,and we will return it to process
void(*old)() = _malloc_alloc_oom_handler_hjstl;
//set the new handler
_malloc_alloc_oom_handler_hjstl = handler;
return (old);
}
};//end of malloc alloc first
///---impelment the first allocate of hjstl
template<int inst>
void* _malloc_alloc_first<inst>::oom_malloc(size_t size)
{
//this is the oom handler,this function will use this function when
//out of memory
void(*my_malloc_handler)();
void* result;
for (;;){//i will loop to test till i get the memory
my_malloc_handler = _malloc_alloc_oom_handler_hjstl;
if (0 == my_malloc_handler) { _THROW_BAD_ALLOC; }
//else,the handler defined,i can use it,run this handler
(*my_malloc_handler)();
result = malloc(size);
if (result) return result;//succeed!!!
}
}
//same as malloc
template<int inst>
void* _malloc_alloc_first<inst>::oom_remalloc(void* old, size_t size)
{
void (* my_handler)();
void* result;
for (;;){
my_handler = _malloc_alloc_oom_handler_hjstl;
if (0 == myhandler) { _THROW_BAD_ALLOC; }
(*my_handler)();
result = realloc(old, size);
if (result) return result;
}
}
//ok,i need to define a first allocate,and if the second
//allocate can not do it,let me try!
typedef _malloc_alloc_first<0> malloc_alloc_first_hjstl;
//we do this,because we need to let the HJSTL standard with STL
template<class Type,class HJALLOC>
class STD_STL_Alloc{
public:
static Type* allocate(size_t size){
return 0 == n ? 0 : (Type*)HJALLOC::allocate(size*sizeof(Type));
}
static Type* allocate(){
return (Type*)HJALLOC::allocate(sizeof(Type));
}
static void deallocate(Type* mem,size_t size){
if (size != 0){
HJALLOC::deallocate(mem,size*sizeof(Type));
}
}
static void deallocate(Type* mem){
HJALLOC::deallocator(mem,sizeof(mem));
}
};
//ok,start to write the second allocator
//and some auxiliary variable neeed to define here
#define _ALIGN 8 //the min mem block
#define _MAX_BYTES 128 //the max mem block
#define _NFREELISTS _MAX_BYTES/_ALIGN //this is the num of free list
template<int inst>//no thread supposed
class _malloc_alloc_second{
private:
//round up a size
static size_t HJSTL_ROUND_UP(size_t bytes){
//ok,i use the SGI STL's round up method,it nice
return (((bytes)+_ALIGN - 1) & ~(_ALIGN - 1));
}
//this function will find the free list's position in the array actually
static size_t HJSTL_FREELIST_INDEX(size_t bytes){
//i just copy the sgi stl's code here.
return (((bytes)+_ALIGN - 1) / _ALIGN - 1);
}
//this is the free list's node strcture
//this is a usion.just copy the sgi stl's
union free_list_node{
free_list_node* free_list_link;//store the next free list
char data[1];//if assigned,this is the user's data
};
//this is the array of free lists
static free_list_node* free_list[_NFREELISTS];
//return an object of size n,and this function will re-fill the
//free list,update the free list array .
static void* refill(size_t size);
//memory pool,and allocate a chunk for nobjs of size 'size'
//if the memory of pool is not so enough,this function will
//return the real num of nobjs
static char* chunk_alloc(size_t size, int &nobjs);
//this is a sample memory pool
static char* pool_free_start;
static char* pool_free_end;
static size_t heap_size;
//the second allocate
static void* allocate(size_t size)
{
free_list_node* my_free_list;
free_list_node* result;
//dispatch the job,if the ask memory bigger _MAX_BYTES,use
//the first allocate,else use the second allocate
if (size > (size_t)_MAX_BYTES){
return (malloc_alloc_first_hjstl::allocate(size));
}
//else ,use the second allocate to do this job
//find the aim-free list
my_free_list = free_list + HJSTL_FREELIST_INDEX(size);
//now,the result is the header pointer of this list
result = *my_free_list;
if (0 == result){//shirt,this list is empty,i will call refill to help me
void* r = refill(HJSTL_ROUND_UP(size));
return r;
}
//else,this free list is not empty,just use the header pointer
//and change the free list.
*my_free_list = result->free_list_link;
return result;
}
//the second deallocate
static void deallocate(void* mem, size_t size)
{
free_list_node* my_free_list;
free_list_node* new_header_pointer = (free_list_node*)mem;
//dispatch the job
if (size > (size_t)_MAX_BYTES){
malloc_alloc_first_hjstl::deallocate(mem, size);
return;
}
//else,solve it.
//first of all,i will find the free list's position
my_free_list = free_list + HJSTL_FREELIST_INDEX(size);
//change the list
new_header_pointer->free_list_link = my_free_list;
*my_free_list = new_header_pointer;
return;
}
//this is the second reallocate
static void* reallocate(void* old, size_t old_size, size_t new_size);
};//end of second allocator
//heihei,this is the default alloc,we will use this alloc in our project
typedef _malloc_alloc_second<0> hjstl_alloc;
#endif //end of _HJ_STL_ALLOC_H_
上面是今天写的代码,很多内容复制了SGI的写法,因为过于经典,在效率面前就不露三脚猫功夫了,下一节将接着实现refill函数和内存池函数,最后所有的内存操作除了不能使用之外都会使用我自己写的一个内存池管理单元,我将单独学习与记录内存池部分。
——今天好傻,压力很大,大三不好玩—
胡建,南开,2016