从STL标准库ALLOC中学习内存管理
STL学习的第一个话题就是内存分配,也是各类容器和算法的基础。每一个容器均配有一个内存分配器用于管理内存。一般情况下,我们没有必要去实现一个内存分配器,编译器会默认给配置一个功能很强大的内存管理器,也是我们学习的重点。默认的内存管理器有以下特点:
1、对大块内存和小块内存的申请,分别进行处理;例如大于128byte的调用malloc直接分配。小于128byte的维护一个内存池进行动态分配。
2、对小块内存的管理通过维护字节为8、16、24、32等不同长度的单链表进行动态的分配与回收,进一步提高效率并且减少内存片。
3、采用union结构产生单链表,减少next_ptr带来的额外内存消耗
模拟STL中对小块内存的管理,实现了一个对内存管理的程序,详细解释如下:
头文件:
// Created/Modified Time: Sat 13 Jul 2013 09:54:06 PM CST
// File Name: test_alloc.h
// Description:
#ifndef HOME_FANGQINGAN_FQA_PROGRAM_TEST_ALLOC_H_
#define HOME_FANGQINGAN_FQA_PROGRAM_TEST_ALLOC_H_
namespace test {
// 最小内存管理单位
const int kMinAlign = 8; // min alloc mem
const int kMaxAlign = 128; // 最大内存管理单位
const int kNFreeList = kMaxAlign / kMinAlign; // 需要维护的单链表的个数
const int kMinChunk = 20; // 内存扩充时最小倍数
union obj {
union obj* free_list_link; // 内部使用free_list_link进行单链表的串接
char clint_data[1]; // 给用户的数据
};
class MyAlloc {
public:
// 内存管理的两个函数,申请和释放
static void *allocate(size_t n);
static void deallocate(void *p, size_t n);
private:
static void *refill(size_t n);
static char* chunk_alloc(size_t size, int& nobjs);
// main vars
static obj* free_list[kNFreeList]; // 单链表数组
static char* start_free;//内存池开始位置
static char * end_free;//内存池结束位置
static size_t heap_size;//已经分配内存大小
};
}
#endif // HOME_FANGQINGAN_FQA_PROGRAM_TEST_ALLOC_H_
源文件:
// Created/Modified Time: Sat 13 Jul 2013 10:04:22 PM CST
// File Name: test_alloc.cc
// Description:
#include "test_alloc.h"
namespace test {
// static vars
obj*MyAlloc::free_list[kNFreeList] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
char *MyAlloc::start_free = 0;
char* MyAlloc::end_free = 0;
size_t MyAlloc::heap_size = 0;
// 申请申请内存大小,确定使用哪个链表结构
size_t free_list_index(size_t bytes) {
return (bytes + kMinAlign-1)/kMinAlign-1;
}
// 对于不足8字节的 进行提升
size_t round_up(size_t bytes) {
return (bytes + kMinAlign - 1) & ~(kMinAlign-1);
}
// 分配内存
void* MyAlloc::allocate(size_t n) {
if (n>kMaxAlign) { // no deal
return 0;
}
obj** my_free_list = free_list + free_list_index(n);
obj* result = *my_free_list;
if(result == 0) {
// 如果某链表为空,则从内存池中分配一些到该链表
void *r = refill(round_up(n));
return r;
}
*my_free_list = result->free_list_link;
return result;
}
void MyAlloc::deallocate(void *p, size_t n) {
if (n > kMaxAlign)
return;
obj** my_free_list = free_list + free_list_index(n);
obj* q = (obj*)p;
q->free_list_link = my_free_list;
*my_free_list = q;
}
// 用于填充空的链表结构
void *MyAlloc::refill(size_t n) {
int nobjs = kMinChunks;
// 从内存池中申请内存,nobjs为引用类型,返回能够申请到的内存块个数
char * chunk = chunk_alloc(n, nobjs);
obj** my_free_list;
obj* current_obj, *next_obj;
if(nobjs == 1)
return chunk;
my_free_list = free_list + free_list_index(n);
obj* result = (obj*)chunk;
*my_free_list = next_obj = (obj*)(chunk + n);
// 将多余的内存块插入到链表中,等同于单链表的标头插入
for (int i = 1;;i++) {
current_obj = next_obj;
next_obj = (obj*)((char*)next + n);
if (nobjs - 1 == i) {
current_obj->free_list_link = 0;
break;
} else {
current_obj->free_list_link = next_obj;
}
}
return result;
}
// 申请nobjs块空间,如果不够则从系统内存中获取
char *MyAlloc::chunk_alloc(size_t size, int& nobjs) {
char *result;
size_t total_bytes = size* nobjs;
size_t left_bytes = end_free - start_free;
if (left_bytes >= total_bytes) {
result = start_free;
start_free += total_bytes;
return result;
}
// 有多少分配多少
if (left_bytes >= size) {
nobjs = left_bytes / size;
total_bytes = size * nobjs;
result = start_free;
start_free += total_bytes;
return result;
}
// 每次从系统申请空间时,多申请一些,至少为需求的两倍。
size_t bytes_to_get = 2* total_bytes + round_up(heap_size) >> 4;
if (left_bytes > 0) {
obj**my_free_list = free_list + free_list_index(left_bytes);
(obj*)start_free->free_list_link = *my_free_list;
*my_free_list = (obj*)start_free;
}
start_free = (char *)malloc(bytes_to_get);
if (start_free == 0) {
nobjs = 0;
return 0;
}
end_free = start_free = bytes_to_get;
heap_size += bytes_to_get;
chunk_alloc(size, nobjs);
}
}
总结
对内存池管理的目的减少大量申请和释放空间给程序带来时间开销,同时也能够减少内存碎片。实际应用中可以直接通过对象池的方法,对实际对象进行管理。