前言


  以STL的实现角度而言,第一个需要介绍的就是空间配置器,因为整个STL的操作对象都存放在容器之中。

  你完全可以实现一个直接向硬件存取空间的allocator。

  下面介绍的是SGI STL提供的配置器,配置的对象,是内存。(以下内容来自《STL源码剖析》)

引子


  因为这篇写得太长,断断续续都有几天,所以先在这里整理一下思路。

  • 首先,介绍 allocator 的标准接口,除了拥有一些基本的typedef之外,最重要的就是内存相关的 allocate 和 deallocate;构造相关的 construct 和 destroy。(两者分离)然后就是实现一个简单的配置器,没有内存管理,只是简单的malloc。

    • allocate 和 deallocate 负责获取可以用的内存。
    • construct调用placement new构造函数,destroy调用相应类型的析构函数 ~T()
  • 然后介绍了SGI的第一级和第二级配置器。定义__USE_MALLOC可以设置使用第一级配置器还是两个都用。
    • 内存池保留没有被分配到free list的空间,free list维护一张可供调用的空间链表。
  • construct 会使用placement new构造,destroy借助traits机制判断是否为 trivial再决定下一步动作。
  • allocate调用refill函数,会缺省申请20个区块,一个返回,19个留在free list。refill又有三种情况。
  • deallocate先判断是否大于128byte,是则调用第一级配置器,否就返回给freelist。

空间配置器的标准接口


根据STL的规范,allocator的必要接口

  • 各种typedef

     allocator::value_type
    allocator::pointer
    allocator::const_pointer
    allocator::reference
    allocator::const_reference
    allocator::size_type
    allocator::difference_type
    allocator::rebind // class rebind<U>拥有唯一成员other;是一个typedef,代表allocator<U>
  • 默认构造函数和析构函数,因为没有数据成员,所以不需要初始化,但是必须被定义
     allocator::allocator()
    allocator::allocator(const allocator&)
    template <class U> allocator::allocator(const allocator<U>&)
    allocator::~allocator()
  • 初始化,地址相关函数
     // 配置空间,足以存储n个T对象,第二个参数是提示,能增进区域性
    pointer allocator::allocate(size_type n, const void*=) size_type allocator::max_size() const pointer allocator::address(reference x) const
    const_pointer allocator::address(const_reference x) const
  • 构建函数
     void allocator::construct(pointer p, const T& x)
    void allocator::destory(pointer p)

自己设计一个简单的空间配置器

 #ifndef __VIGGO__
#define __VIGGO__
#include <new> // for placement new
#include <cstddef> // for ptrdiff_t, size_t
#include <cstdlib> // for exit()
#include <climits> // for UINT_MAX
#include <iostream> // for cerr namespace VG { template <class T>
inline T* _allocate(ptrdiff_t n, T*) {
set_new_handler();
T* tmp = (T*)(::operator new((size_t)(n * sizeof(T))));
if (tmp == ) {
cerr << "alloc memory error!" << endl;
exit();
}
return tmp;
} template <class T>
inline void _deallocate(T* p) {
::operator delete(p);
} template <class T1, class T2>
inline void _construct(T1* p, const T2& value) {
new(p) T1(value);
} template <class T>
inline void _destroy(T* p) {
p->~T();
} 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;
typedef ptrdiff_t difference_type; template <class U>
struct rebind {
typedef allocator<U> other;
}; pointer address(reference x) {return (pointer)&x;}
const_pointer address(const_reference x) const {
return (const_pointer)&x;
} pointer allocate(size_type n, const void *hint=) {
return _allocate((difference_type)n, (pointer)); // mark
} void deallocate(pointer p, size_type n) {
_deallocate(p);
} size_type max_size() const {return size_type(UINT_MAX / sizeof(T));} void construct(pointer p, const T& x) {
_construct(p, x);
} void destroy(pointer p) {
_destroy(p);
}
};
}
#endif

  放在 vector<int, VG::allocator<int> > 中测试,可以实现简单的内存分配,但是实际上的 allocator 要比这个复杂。

SGI特殊的空间配置器


  标准的allocator只是基层内存配置/释放行为(::operator new 和 ::operator delete)的一层薄薄的包装,并没有任何效率上的强化。

  现在我们看看C++内存配置和释放是怎样做的:

  new运算分两阶段(1)调用 ::operator new 配置内存;(2) 调用对象构造函数构造对象内容。

  delete运算也分两阶段(1) 调用对象的析构函数;(2)调用 ::operator delete 释放内存。

  为了精密分工,STL allocator决定将两阶段操作区分开来,内存配置由 alloc::allocate() 负责。内存释放操作由 alloc::deallocate()负责;对象构造由 ::construct() 负责,对象析构由 ::destroy() 负责。

构造和析构基本工具:construct() 和 destroy()


  construct() 接受一个指针p和一个初值value,该函数的用途就是将初值设定到指针所指的空间上。C++的placement new运算子可用来完成这一任务。

  destory()有两个版本,一是接受一个指针,直接调用该对象的析构函数即可。另外一个接受first和last,将半开范围内的所有对象析构。首先我们不知道范围有多大,万一很大,而每个对象的析构函数都无关痛痒(所谓 trivial destructor),那么一次次调用这些无关痛痒的析构函数是一种浪费。所以我们首先判断迭代器所指对象是否为 trivial(无意义), 是则什么都不用做;否则一个个调用析构。

上图为construct的实现函数

上图为destroy的实现函数

这里用到我们神奇的 __type_traits<T>,之前介绍的 traits 是 萃取返回值类型 和 作为重载依据的,现在为每一个内置类型特化声明一些tag。

现在我们需要用到 两个标志:

示例:

空间的配置和释放:std::alloc


  SGI的设计哲学: 1. 向 system heap 要求空间; 2. 考虑多线程状态(先略过);3. 考虑内存不足时的应变措施;4. 考虑过多“小型区块”可能造成的内存碎片问题。

  SGI设计了双层级配置器,第一级配置器直接使用 malloc() 和 free(),第二级配置器则视情况采用不同的策略;当配置区块超过128bytes时,交给第一级配置器。

  整个设计究竟只开放第一级配置器,或是同时开放第二级配置,取决于__USE_MALLOC时候被定义:

 # ifdef __USE_MALLOC
...
typedef __malloc_alloc_template<> malloc_alloc;
typedef malloc_alloc alloc; // 令alloc为第一级配置器
#else
...
// 令alloc为第二级配置器
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, >alloc;
#endif

  其中__malloc_alloc_template就是第一级配置器,__default_alloc_template为第二级配置器。alloc并不接受任何template型别参数。

  无论alloc被定义为第一级或第二级配置器,SGI还为它在包装一个接口如下,使配置器的接口能够符合STL规格:

 template <class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n)
{return ==n? : (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 ( != n) Alloc::deallocate(p, n*sizeof(T));}
static void deallocate(T *p)
{Alloc::deallocate(p, sizeof(T));}

  一二级配置器的关系,接口包装,及实际运用方式,

第一级配置器 __malloc_alloc_template


 #if 0
# include <new>
# define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
# include <iostream>
# define __THROW_BAD_ALLOC cerr << "out of memery" << endl; exit();
#endif // malloc-based allocator.通常比稍后介绍的 default alloc 速度慢
// 一般而言是thread-safe,并且对于空间的运用比较高效
// 以下是第一级配置器
// 注意,无“template型别参数”。置于“非型别参数”inst,则完全没排上用场
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) {
void *result = malloc(n); // 第一级配置器直接使用malloc
// 无法满足需求时,改用oom_malloc
if ( == result) result = 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) {
void *result = realloc(p, new_sz);
if ( == result) result = oom_realloc(p, new_sz);
return result;
} // 以下仿真C++的 set_handler()。换句话,你可以通过它
// 指定自己的 out-of-memory handler,企图释放内存
// 因为没有调用 new,所以不能用 set_new_handler
static void (* set_malloc_handler(void (*f)())) () {
void (*old)() = __malloc_alloc_oom_handler;
__malloc_alloc_oom_handler = f;
return old;
}
}; // 初值为0,待定
template <int inst>
void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = ; 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 ( = my_malloc_handler) {__THROW_BAD_ALLOC;} // 如果没设置
(* my_malloc_handler)(); // 调用处理例程,企图释放内存
result = malloc(n); // 再次尝试配置内存
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 ( == my_malloc_handler) {__THROW_BAD_ALLOC;}
(*my_malloc_handler)();
result = realloc(p, n);
if (result) return result;
}
}

第二级配置器 __default_alloc_template


空间配置函数 - allocate()

 static void * allocate(size_t n);

1. 如果 n 大于128bytes的时候,交给第一级配置器。

2. 找到 n 对应free list下的节点;如果节点不可用(=0)则调用 refill() 填充,否则调整节点指向下一个为止,直接返回可用节点。

重新填充free lists - refill()

void * refill(size_t n); //缺省取得20个节点

把大小为 n 的区块交给客户,然后剩下的19个交给对应的 free_list 管理。

内存池 - chunk_alloc()

char * chunk_alloc(size_t size, int & nobjs); // nobjs是引用,会随实际情况调整大小

申请内存分三种情况:

  • 内存池剩余空间完全满足需求。
  • 内存池剩余空间不能完全满足需求量,当足够供应一个(含)以上的区块。
  • 内存池剩余空间连一个区块的大小都无法提供。

首先必须做的就是查看剩余的空间:

 size_t bytes_left = end_free - start_free;
size_t total_bytes = size * nobjs;

面对第一种情况,内存空间足够的,只需要调整代表空闲内存的 start_free 指针,返回区域块就可以。

面对第二种情况,尽量分配,有多少尽量分配。这是nobjs会被逐渐减少,从默认的20到能分配出内存, nobjs = bytes_left / size。

面对第三种情况,情况有点复杂。

  • 既然 [start_free, end_free) 之间的空间不够分配 size * nobjs 大小的空间,就先把这段空间分配给合适的 free list 节点(下一步有用)。
  • 从 heap 上分配 两倍的所需内存+heap大小的1/16(对齐成8的倍数) 大小的内存。
    • 如果heap分配都失败的话,就在 free list 中比 size 大的节点中找内存使用。
    • 实在不行只能调用第一级配置器看看有咩有奇迹,oom机制。
  • 最后调整 heap_size 和 end_free,递归调用 chunk_alloc 知道至少能分出一个区块。

空间释放函数 - deallocate()

大于128就交给第一级配置器,否则调整free list,释放内存。

完整代码

 enum {__ALIGN = };
enum {__MAX_BYTES = };
enum {_NFREELISTS = __MAX_BYTES/__ALIGN}; // 以下是第二级配置器
// 注意,无“template型别参数”,且第二参数完全没排上用场
// 第一参数用于多线程环境下
template <bool threads, int inst>
class __default_alloc_template {
private:
// 将bytes上调至8的倍数
static size_t ROUND_UP(size_t bytes) {
return ((bytes) + __ALIGN-) & ~(__ALIGN-);
} union obj { // free-lists的节点构造
union obj *free_list_link;
char client_data[];
}; static obj *volatile free_list[_NFREELISTS];
static size_t FREELIST_INDEX(size_t bytes) {
return ((bytes) + (__ALIGN-)) / (__ALIGN-);
} // 返回一个大小为n的对象,并可能加入大小为n的其他区块到free list
static void *refill(size_t n);
// 配置一大块空间,可容纳 nobj 个大小为“size”的区块
// 如果配置 nobjs 个区块有所不便,nobjs可能会降低
static char *chunk_alloc(size_t size, int &nobjs); // Chunk allocation state
static char *start_free; // 内存池起始位置,只在chunk_alloc中变化
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_t old_sz, size_t new_sz);
}; template <bool threads, int inst>
char * __default_alloc_template<threads, inst>::start_free = ; template <bool threads, int inst>
char * __default_alloc_template<threads, inst>::end_free = ; template <bool threads, int inst>
size_t * __default_alloc_template<threads, inst>::heap_size = ; template <bool threads, int inst>
__default_alloc_template<threads, inst>::obj *volatile
__default_alloc_template<threads, inst>::free_list[_NFREELISTS] =
{, , , , , , , , , , , , , , , }; // n must > 0
template<bool threads, int inst>
void * __default_alloc_template<threads, inst>::allocate(size_t n) {
obj * volatile * my_free_list; // 一个数组,数组元素是obj*
obj * result; if (n > (size_t) __MAX_BYTES) {
return malloc_alloc::allocate(n);
} // 寻找16个free lists中适当的一个
my_free_list = free_list + FREELIST_INDEX(n);
result = *my_free_list;
if (result == ) {
// 没找到可用的free list,准备重新填充free list
void *r = refill(ROUND_UP(n));
return r;
} // 调整free list
*my_free_list = result -> free_list_link;
return result;
} template <bool threads, int inst>
void __default_alloc_template<threads, inst>::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;
} template <bool threads, int inst>
void * __default_alloc_template<threads, inst>::refill(size_t n) {
int nobjs = ;
// 调用chunk_alloc(),尝试取得nobjs个区块作为free list的新节点
// 注意参数nobjs是pass by reference
char * chunk = chunk_alloc(n, nobjs);
obj * volatile * my_free_link;
obj * result;
obj * current_obj, * next_obj;
int i; // 如果只获得一个区块,这个区块就分配给调用者用,free list无新节点
if ( == nobjs) return chunk;
// 否则准备调整free link,纳入新节点
my_free_link = free_list + FREELIST_INDEX(n); // 以下是chunk空间内建立free list
result = (obj *)chunk;
// 以下引导free list指向新配置的空间(取自内存池)
*my_free_link = next_obj = (obj*) (chunk + n);
// 以下将free list的各节点串接起来
for (i=; ; ++i) { // 从1开始,因为第0个将返回给客户端
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + n);
if (nobjs - == i) {
current_obj -> free_list_link = ;
break;
} else {
current_obj -> free_list_link = next_obj;
}
}
return result;
} // 假设size已经上调至8的倍数
// 注意参数nobjs是pass by reference
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 {
// 内存池剩余空间连一个区块的大小都无法提供
size_t bytes_to_get = * total_bytes + ROUND_UP(heap_size >> );
// 以下试着让内存池中的残余零头还有利用价值
if (bytes_left > ) {
// 内存池内还有一些零头,先配给适当的free list
// 首先寻找适当的free list
obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
// 调整free list,将内存池中的残余空间编入
((obj *)start_free) -> free_list_link = *my_free_list;
*my_free_list = (obj *)start_free;
} // 配置heap空间,用来补充内存池
start_free = (char *)malloc(bytes_to_get);
if ( == start_free) {
// heap空间不足,malloc失败
int i;
obj * volatile * my_free_list, *p;
// 试着检视我们手上拥有的东西,这不会造成伤害。我们不打算尝试配置
// 较小的区块,因为那在多进程机器上容器导致灾难
// 以下搜寻适当的free list
// 所谓适当是指“尚未用区块,且区块够大”的free list
for (i=size; i <= __MAX_BYTES; i+=__ALIGN) {
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if ( != p) { // free list内尚有未用块
// 调整free list以释放未用区块
*my_free_list = p -> free_list_link;
start_free = (char *)p;
end_free = start_free + i;
// 递归调用自己,为了修正nobjs
return chunk_alloc(size, nobjs);
// 注意,任何残余零头终将被编入适当的free list中备用
}
}
end_free = ; // 如果出现意外,调用第一级配置器,看看oom机制能否尽力
start_free = (char *)malloc_alloc::allocate(bytes_to_get);
// 这会抛出异常 或 内存不足的情况得到改善
}
heap_size += bytes_to_get;
end_free = start_free + bytes_to_get;
// 递归调用自己,为了修正nobjs
return chunk_alloc(size, nobjs);
}
}

STL源码剖析 — 空间配置器(allocator)的更多相关文章

  1. STL源码剖析——空间配置器Allocator#2 一/二级空间配置器

    上节学习了内存配置后的对象构造行为和内存释放前的对象析构行为,在这一节来学习内存的配置与释放. C++的内存配置基本操作是::operator new(),而释放基本操作是::operator del ...

  2. STL源码剖析——空间配置器Allocator#1 构造与析构

    以STL的运用角度而言,空间配置器是最不需要介绍的东西,因为它扮演的是幕后的角色,隐藏在一切容器的背后默默工作.但以STL的实现角度而言,最应该首先介绍的就是空间配置器,因为这是这是容器展开一切运作的 ...

  3. STL源码剖析——空间配置器Allocator#3 自由链表与内存池

    上节在学习第二级配置器时了解了第二级配置器通过内存池与自由链表来处理小区块内存的申请.但只是对其概念进行点到为止的认识,并未深入探究.这节就来学习一下自由链表的填充和内存池的内存分配机制. refil ...

  4. STL源码剖析(空间配置器)

    前言 在STL中,容器的定义中都带一个模板参数,如vector template <class T, class Alloc = alloc> class vector {...} 其中第 ...

  5. STL源码剖析:配置器

    作用:对内存的管理 接口:申请和释放 内容: 几个全局函数 一级配置器 二级配置器 准备知识 POD是什么: Plain Old Data简称POD,表示传统的C语言类型:与POD类型对应的是非POD ...

  6. STL学习笔记:空间配置器allocator

    allocator必要接口: allocator::value_type allocator::pointer allocator::const_pointer allocator::referenc ...

  7. 《STL源码剖析》相关面试题总结

    原文链接:http://www.cnblogs.com/raichen/p/5817158.html 一.STL简介 STL提供六大组件,彼此可以组合套用: 容器容器就是各种数据结构,我就不多说,看看 ...

  8. 面试题总结(三)、《STL源码剖析》相关面试题总结

    声明:本文主要探讨与STL实现相关的面试题,主要参考侯捷的<STL源码剖析>,每一个知识点讨论力求简洁,便于记忆,但讨论深度有限,如要深入研究可点击参考链接,希望对正在找工作的同学有点帮助 ...

  9. STL源码剖析之空间配置器

    本文大致对STL中的空间配置器进行一个简单的讲解,由于只是一篇博客类型的文章,无法将源码表现到面面俱到,所以真正感兴趣的码农们可以从源码中或者<STL源码剖析>仔细了解一下. 1,为什么S ...

随机推荐

  1. Spring Boot 定时任务的使用

    @Configuration @EnableScheduling public class ScheduleConfig { private final Logger logger = LoggerF ...

  2. Android_Jar mismatch! Fix your dependencies

    在用adt开发安卓时,添加依赖的library后,经常会出现错误,Jar mismatch! Fix your dependencies 这个错误的原因是.出现了不同版本的jar包(例如:V4包版本不 ...

  3. android中与SQLite数据库相关的类

    为什么要在应用程序中使用数据库?数据库最主要的用途就是作为数据的存储容器,另外,由于可以很方便的将应用程序中的数据结构(比如C语言中的结构体)转化成数据库的表,这样我们就可以通过操作数据库来替代写一堆 ...

  4. MIPCMS V3.1.0 远程写入配置文件Getshell过程分析(附批量getshell脚本)

      作者:i春秋作家--F0rmat 0×01 前言 今天翻了下CNVD,看到了一个MIPCMS的远程代码执行漏洞,然后就去官网下载了这个版本的源码研究了下.看下整体的结构,用的是thinkPHP的架 ...

  5. C语言程序设计(基础)- 第2周作业

    1.阅读提问的智慧,要求仔细阅读链接内容,用自己的话描述你的收获,并举例子说明应该如何提问. 2.所有同学请在自己电脑上配置git.编译器(win10 系统的话就Dev-C++).翻译软件,十一回校后 ...

  6. python3变量和数据类型

        变量和数据类型 知识点 python 关键字 变量的定义与赋值 input() 函数 字符串的格式化 实验步骤 每一种编程语言都有它们自己的语法规则,就像我们所说的外语. 1. 关键字和标识符 ...

  7. MySQL 服务安装及命令使用

    MySQL 服务安装及命令使用 课程来源说明 本节实验后续至第17节实验为本课程的进阶篇,都基于 MySQL 官方参考手册制作,并根据实验楼环境进行测试调整改编.在此感谢 MySQL 的开发者,官方文 ...

  8. SCOI2010 序列操作

    2421 序列操作 http://codevs.cn/problem/2421/ 2010年省队选拔赛四川   题目描述 Description lxhgww最近收到了一个01序列,序列里面包含了n个 ...

  9. 原始的Ajax方法 (异步的 JavaScript 和 XML -- (Extensible Markup Language 可扩展标记语言))

    <script language="javascript" type="text/javascript"> var request = false; ...

  10. Nginx配置小结

    前两天区听了一堂Nginx的课,然后翻了一下自己之前的Nginx的笔记,做了一个简单的小结. 全局变量 $args : 这个变量等于请求行中的参数,同$query_string $content_le ...