记录一下自己对于分配器的学习笔记以及代码。和测试用例;
这个是在学习《STL源码刨析》书籍后,写的数据结构和函数;
内存池代码实现:
// alloc.h
#ifndef ALLOC_H
#define ALLOC_H
#include<iostream>
#include <cstddef>
#include <cstdio>
namespace mystl {
// static是在这个作用域中,使用了static就会使得不会在局部使用后被析构了,生命周期变长。别的作用域看不见;
//而静态局部变量不能被extern
// 所以使用static进行成员函数的修饰时候,此函数并不属于任何一个对象,而是属于这个类;
// static的数据成员要在类外(无论什么修饰)进行初始化,但是private的成员要在类内进行,或者构造函数处;
int ALIGN = 8;
union Free_List {
Free_List* free_link; // 是指向下一个待分配空间的指针
char data[1]; // 其实好像没啥用
};
// int ALIGN = 8;
class alloc{
// 访问类中成员权限的限制符,其实针对的是继承,以及内外的试用权限;
private:
static size_t upbound(size_t n); // 将n变为8的倍数
static void* refill(size_t n); // 如果哪个链上没空间了,就去补充
static void* pool_alloc(size_t n, size_t& alloc_num); // 在内存池中找n大小的内存空间
private:
static Free_List* free_list[16]; // 8、16、24......128 free_list链表
static char* start_free; // 内存池的首地址
static char* end_free; // 内存池的尾地址
static size_t heap_size; // 如果频繁的要内存,heap_size就会增大,内存池就会向heap要更多的内存
public:
static void* allocate(int n); // 客户端问自由链表要n bit的大小空间;
static void dealloc(void* p, size_t n); // 释放 p 指向的大小为 n 的空间, p 不能为 0
static void* realloc(void* p, size_t old_size, size_t new_size); // 将旧的空间进行客户端的释放,将新的地址给客户端进行分配
};
// 因为静态成员属于整个类,而不属于某个对象,如果在类内初始化,会导致每个对象都包含该静态成员,这是矛盾的。
// 类外定义和初始化是保证static成员变量只被定义一次的好方法
Free_List* alloc::free_list[16] = {nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,
nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr,nullptr}; // 类外对静态成员变量进行初始化
char* alloc::start_free = nullptr;
char* alloc::end_free = nullptr;
size_t alloc::heap_size = 0;
// 进行空间分配
void* alloc::allocate(int n) {
std::cout << "您需要的空间:" << n << std::endl;
std::cout << "开始分配空间" << std::endl;
if (n > 128) // 大于128 B时候
{
void* result = malloc(n);
std::cout << "系统直接分配空间!" << std::endl;
return result;
}
size_t num_bit = upbound(n); // 进行多大空间的计算
std::cout << "开始计算空间大小" << num_bit << std::endl;
int node_num = num_bit / ALIGN - 1;
std::cout << "插入的自由链表下标为:" << node_num << std::endl;
Free_List* result = free_list[node_num]; // 里面存的是指向空闲空间的指针
if (free_list[node_num] == nullptr)
{
std::cout << node_num <<"号节点,此刻为空!" << std::endl;
void* result = refill(num_bit);
std::cout << "重新填充完毕,分配空间结束!" << std::endl;
return result;
}
free_list[node_num] = result->free_link;
std::cout << "自由链表有空间,链表分配空间结束!" << std::endl;
return result;
}
void alloc::dealloc(void* p, size_t n) // 将 p指向的空间进行释放
{
if (n > 128)
{
std::free(p);
return;
}
p = (Free_List*)p;
int index = upbound(n) / ALIGN; // 找到index
Free_List* myfree_list = free_list[index];
// myfree_list->free_link = p->free_link;
std::free(p);
}
// 进行空间的重新分配
void* alloc::realloc(void* p, size_t old_size, size_t new_size)
{
// 释放旧的空间
void* result;
if (old_size > 128)
std::free(p);
else {
size_t index = upbound(old_size) / ALIGN;
// free_list[index] = (Free_List*)p->free_link;
std::free(p);
}
// 分配新的空间
if (new_size > 128) {
void* result = malloc(new_size);
}
else {
void* result = allocate(new_size);
}
return result;
}
/*
调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。
然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。
接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。
调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,
如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。
于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,
将相邻的小空闲块合并成较大的内存块。
通常意义上我们malloc(n * sizeof(char) 其实就是动态分配n个B 1Byte=8bit
*/
// 进行空间大小的8倍镜
size_t alloc::upbound(size_t n){
return ((n + ALIGN - 1) & ~(ALIGN - 1));
}
void* alloc::refill(size_t n) {
// pool_alloc()
std::cout << "开始填充链表!" << std::endl;
size_t alloc_num = 20; // 一次性分配20个大小空间
size_t bytes = upbound(n);
// size_t index = n / __ALIGN - 1;
char* chunk = (char*)pool_alloc(bytes, alloc_num); // 从内存池中找空间
if (alloc_num == 1)
{
std::cout << "会从这里返回吗?"<< std::endl;
return chunk;
}
std::cout << "从内存池拿到的内存大小为:" << alloc_num << std::endl;
Free_List* result;
Free_List* cur;
// Free_List* next = (Free_List*) (chunk + bytes); // 就是下一个要给链表的节点
result = (Free_List*) chunk; // 返回客户端一个,剩下的自己用
Free_List* myfree_list = free_list[upbound(n) / ALIGN - 1];
Free_List* next = (Free_List*)(chunk + bytes);
myfree_list = next;
cur = myfree_list;
// myfree_list = cur; // 指向下一个节点
std::cout << "开始自由链表的串联!" << std::endl;
for (int i = 1; ; i++) {
cur = next;
next = (Free_List*) ((char*) next + bytes);
if (alloc_num - 1 == i)
{
cur->free_link = 0;
break;
}
else
cur->free_link = next;
}
std::cout << "链表填充完毕!" << std::endl;
return result;
}
void* alloc::pool_alloc(size_t n,size_t& alloc_num) {
std::cout << "开始填充内存池" << std::endl;
void* result;
size_t total_get = n * alloc_num;
size_t total_mem = end_free - start_free;
if (total_get <= total_mem) {
result = start_free;
start_free = start_free + total_get;
// return result;
}
else if (total_mem > n){
// 内存池空间不足了
alloc_num = total_mem / n; // 最多分配的个数
total_get = alloc_num * n; // 最多分配的空间
result = start_free;
start_free = start_free + total_get;
}
else { // 也就是说一个空间都给不了了
size_t bytes_get = 2 * total_get;
start_free = (char*) malloc(bytes_get); // 分配双倍的空间
end_free = start_free + bytes_get;
result = start_free;
start_free = start_free + total_get;
// return result;
}
std::cout<< "内存池填充完毕!" << std::endl;
return result;
}
}
#endif
测试用例的实现:
#include<iostream>
#include"alloc.h"
using namespace std;
using namespace mystl;
void test_alloc();
int main() {
// test_alloc(); alloc分配器勉强通过测试
return 0;
}
void test_alloc() {
cout << "***开始测试alloc分配器啦***" << endl;
alloc a;
void* client = a.allocate(45);
free(client); // 重新分配空间没问题
}
代码实现结果(只测试分配12、45、129时候的结果)
需要内存大小为45时候,alloc的结果;
需要内存大小为12时候,alloc的结果;
需要内存大小为129时候,alloc的结果;
由于自由链表下面挂着有空间时候,我们刚好需要此index下空间,难以测试,也懒得测,就不会进行测试了,感觉逻辑应该问题不大,大差不差的。 over,记录在此!