对于创建自己的STL库来说,重点就在于alloc配置器,其他大部分还是好理解的
alloc 配置器
std::alloc在源码中考虑小型区块的碎片化问题,存在两级配置器:
- 第一级配置器用来申请超过内存块容量(128byte)的内存,并管理
第二级的作用是完成std::alloc对内存的分配。
一级配置器主要在于malloc
和free
的应用,就不做多的解释,配置器的精髓都在二级配置器中。
二级配置器
二级空间配置器使用内存池+自由链表的形式避免了小块内存带来的碎片化,提高了分配的效率,提高了利用率。SGI的做法是先判断要开辟的大小是不是大于128,如果大于128则就认为是一块大块内存,调用一级空间配置器直接分配。否则的话就通过内存池来分配,假设要分配8个字节大小的空间,那么他就会去内存池中分配多个8个字节大小的内存块,将多余的挂在自由链表上,下一次再需要8个字节时就去自由链表上取就可以了,如果回收这8个字节的话,直接将它挂在自由链表上就可以了。
为了便于管理,二级空间配置器在分配的时候都是以8的倍数对齐。也就是说二级配置器会将任何小块内存的需求上调到8的倍数处(例如:要7个字节,会给你分配8个字节。要9个字节,会给你16个字节),尽管这样做有内碎片的问题,但是对于我们管理来说却简单了不少。因为这样的话只要维护16个free_list
就可以了,free_list这16个结点分别管理大小为8,16,24,32,40,48,56,64,72,80,88,86,96,104,112,120,128
字节大小的内存块就行了。
自由链表的结点类型:
union obj
{
union obj* free_list_link;
char client_data[1]; /* 这个数据部分,新版本取消了,内存分配中也没有用到,可以直接删除 */
};
内存池模型
已分配:交给free-list来管理
未分配:为内存池,通过stare_free、end_free来描述
head_size:总开辟空间
注意:
-
当pool减少时,就把start_free右移;
-
当pool修改时,就将两根指针指向的位置修改
内存池分配过程
主要函数:
- allocate:通过free-list分配内存
- refill:在内存池中取新块
- chunk_alloc:内存池扩展新块
图中过程的一些说明:
- pool不会在原来的位置上增加,需要更多的pool内存的时候,会把原来pool中的内存视为内存碎片连接到某一个链表上,然后为pool重新分配。
- heap_size用于记录由alloc第二级分配器申请的内存的总大小,也就是现在alloc所可以进行管理的内存的总大小,用于后边计算追加量。
alloc.h
/*
* alloc.h文件是项目的开始部分,无论是哪一个容器,都需要分配内存才能行动,
* alloc.h文件也是内存分配中的最基石
*
* 第一步
*/
#ifndef _ALLOC_H_
#define _ALLOC_H_
#include <cstdlib>
namespace mySTL{
/*
**空间配置器,以字节数为单位分配
**内部使用
*/
class alloc{
private:
enum EAlign{ ALIGN = 8};//小型区块的上调边界
enum EMaxBytes{ MAXBYTES = 128};//小型区块的上限,超过的区块由malloc分配
enum ENFreeLists{ NFREELISTS = (EMaxBytes::MAXBYTES / EAlign::ALIGN)};//free-lists的个数
enum ENObjs{ NOBJS = 20};//每次增加的节点数
private:
// free-lists的节点构造
// 利用union节省了空间,这个区块既可以放下一块的指针,又可以存放数据
// 一级指向另一个obj,二级obj指向实际区块
union obj{
union obj *next;
char client[1]; /* 这个数据部分,新版本取消了,内存分配中也没有用到,可以直接删除 */
};
static obj *free_list[ENFreeLists::NFREELISTS];
private:
static char *start_free; // 内存池起始位置
static char *end_free; // 内存池结束位置
static size_t heap_size; // 堆的大小,总分配大小
// 之所以在这里使用静态内存的原因也很清楚,这些数据都是唯一的,\
// 不可能同时存在两个,一旦改变(一般也不改变)就是全局一起改变
private:
// 将byte上调至8的倍数,1-8范围8,9-16返回16……总是8的倍数
static size_t ROUND_UP(size_t bytes){
return ((bytes + EAlign::ALIGN - 1) & ~(EAlign::ALIGN - 1));
}
// 根据区块大小,决定使用第n号free-list,n从0开始计算
static size_t FREELIST_INDEX(size_t bytes){
return (((bytes)+EAlign::ALIGN - 1) / EAlign::ALIGN - 1);
}
// 返回大小为n的对象,并可能加入大小为n的其他区块到free-list
static void *refill(size_t n);
//配置一大块空间,可容纳nobjs个大小为size的区块
//如果配置nobjs个区块有所不便,nobjs可能会降低
static char *chunk_alloc(size_t size, size_t& nobjs);
public:
// 外部可以调用的部分
// 静态方法效率上比实例化要高,这种经常要用的函数静态是最好了,每次实例化都要成本
static void *allocate(size_t bytes);
static void deallocate(void *ptr, size_t bytes);
static void *reallocate(void *ptr, size_t old_sz, size_t new_sz);
};
}
#endif
alloc.cpp
#include "../Includes/Alloc.h"
namespace mySTL {
// 初始化变量
char* alloc::start_free = 0;
char* alloc::end_free = 0;
size_t alloc::heap_size = 0;
alloc::obj* alloc::free_list[alloc::ENFreeLists::NFREELISTS] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
}; // 16个
void* alloc::allocate(size_t bytes) {
if (bytes > EMaxBytes::MAXBYTES) {
return malloc(bytes);// >128 使用一级配置器
}
// 寻找16个中较为合适的一个
size_t index = FREELIST_INDEX(bytes);
obj* list = free_list[index];
if (list != 0) {//此list还有空间给我们
free_list[index] = list->next; // 把后一块内存的地址存入,便于下次取用
return list; //如果这块内存恰好空着,把这块内存送出
}
else {//此list没有足够的空间,需要从内存池里面取空间
return refill(ROUND_UP(bytes));
}
}
void alloc::deallocate(void* ptr, size_t bytes) {
if (bytes > EMaxBytes::MAXBYTES) {
free(ptr);
}
else {
// 寻找对应的free-list
size_t index = FREELIST_INDEX(bytes);
// 调整free-list ,回收区块
obj* node = static_cast<obj*>(ptr);
node->next = free_list[index];
free_list[index] = node; // 将这块内存放在最前方,之前的内存挂在其后
}
}
void* alloc::reallocate(void* ptr, size_t old_sz, size_t new_sz) {
deallocate(ptr, old_sz);//销毁
ptr = allocate(new_sz);//重分配
return ptr;
}
//返回一个大小为n的对象,并且有时候会为适当的free list增加节点
//假设bytes已经上调为8的倍数
void* alloc::refill(size_t bytes) {
size_t nobjs = ENObjs::NOBJS; // 每次增加节点数
//从内存池里取
char* chunk = chunk_alloc(bytes, nobjs); // 从内存池中,取nobjs个新区块
obj** my_free_list = 0;
obj* result = 0;
obj* current_obj = 0, * next_obj = 0;
if (nobjs == 1)
{
//只够一个对象用,无需分割直接返回
return chunk;
}
else
{
my_free_list = free_list + FREELIST_INDEX(bytes);
result = (obj*)(chunk);
// 将取出的多余的空间加入到相应的free list里面去(取自内存池)
*my_free_list = next_obj = (obj*)(chunk + bytes);
// 将free_list各节点串起来
// 从1开始,因为第0个将返回给客端
for (int i = 1;; ++i)
{
current_obj = next_obj;
next_obj = (obj*)((char*)next_obj + bytes);
if (nobjs - 1 == i)
{
//分得块数足够,内存已经切完,最后一块指向0,退出循环
current_obj->next = 0;
break;
}
else {
current_obj->next = next_obj;
}
}
return result;
}
}
//假设bytes已经上调为8的倍数,内存池计算
char* alloc::chunk_alloc(size_t bytes, size_t& nobjs) {
char* result = 0;
size_t total_bytes = bytes * nobjs;
size_t bytes_left = end_free - start_free;
if (bytes_left >= total_bytes) {//内存池剩余空间完全满足需要
result = start_free;
start_free = start_free + total_bytes;
return result;
}
else if (bytes_left >= bytes) {//内存池剩余空间不能完全满足需要,但足够供应一个或以上的区块
nobjs = bytes_left / bytes;
total_bytes = nobjs * bytes;
result = start_free;
start_free += total_bytes;
return result;
}
else {//内存池剩余空间连一个区块的大小都无法提供
// 开辟2*20 个byte空间,1个交给客端,19个维护,剩下20个给内存池
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
if (bytes_left > 0) {
// 内存池还有参与零头,先给free-list
obj** my_free_list = free_list + FREELIST_INDEX(bytes_left);
// 将残余空间编入
((obj*)start_free)->next = *my_free_list;
*my_free_list = (obj*)start_free;
}
// 配置heap空间补充内存池
start_free = (char*)malloc(bytes_to_get); // 要一块新的内存,并把内存起点重新定义
if (0 == start_free) {
// malloc没有得到内存,heap空间不足
obj** my_free_list = 0, * p = 0;
// 寻找尚未使用,且区块足够大的freelist
for (int i = 0; i <= EMaxBytes::MAXBYTES; i += EAlign::ALIGN)
{
my_free_list = free_list + FREELIST_INDEX(i);
p = *my_free_list;
if (p != 0)
{
//找到碎片,返回
*my_free_list = p->next;
start_free = (char*)p;
end_free = start_free + i;
return chunk_alloc(bytes, nobjs);
}
}
end_free = 0; // 山穷水尽,没有内存可以用,可以试试一级配置器
}
heap_size += bytes_to_get; //扩充内存
end_free = start_free + bytes_to_get; //重定义末尾内存
return chunk_alloc(bytes, nobjs);
}
}
}