C++内存管理(1)alloc运行一瞥
alloc源码剖析
该配置器设计了双层级别:
一级配置器:直接使用malloc、free、以及对内存申请失败的处理;
二级配置器:将采用复杂的内存池来管理
一级配置器实现:
当请求大于128bytes时,使用该分配器
二级配置器实现:
alloc 维护16条链表,每条链表管理不同大小的区块,最小的为8bytes,最大为128bytes。
链表采用了嵌入式指针,定义如下:
union obj{
union obj *next;
char client[1];
};
obj指针既可以看作一个指针,指向一个obj对象,也可以一个指针,指向实际的区块,一物二用。
二级配置器头文件整体代码:
namespace TinySTL{
/*
**空间配置器,以字节数为单位分配
**内部使用
*/
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{
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:
//将bytes上调至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);
};
}
ROUND_UP:内存对齐,将大小调整为8的倍数,如果申请的大小为5,则调整为8
ROUND_UP的设计十分巧妙
一个数k如果正好是2^n的倍数,那么用二进制来 表示的话,低n位必定全是0,而 _ALIGN-1的二进制表示则是低n位全为1,
那么k+ _ALIGN-1和~(_ALIGN – 1)取与必定是k本身;如果一个数k不是2^n的倍数,则必有m*2^n<k<(m+1)*2^n,
要做的就是求出(m+1)*2^n, 则k用二进制来表示的话,低n位肯定不全为0,再加上_ALIGN-1必然会向第n+1位进位,此时若
将低n位全部置零的话,得到刚好就是 (m+1)*2^n,而和~(_ALIGN – 1)取与正好达到这一效果。
这里的关键在于,对一个数如果有m*2^n<k<(m+1)*2^n,那么有 k=m*2^n+p, 且p<2^n,这样k就分成了两部分,
在用二进制表示的时候,p是存储在低n位中,而m*2^n是存储在高位中(即除去低n位的剩余位数),
P+ALIGN-1,肯定向高位进一,即为(m+1)*2^n+P1,然后与全0,则得到(m+1)*2^n=2^(n+1)。
二级配置器源文件整体代码:
namespace TinySTL{
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,
};
void *alloc::allocate(size_t bytes)
{
if (bytes > EMaxBytes::MAXBYTES){
return malloc(bytes);
}
size_t index = FREELIST_INDEX(bytes);
obj *list = free_list[index];
if (list){//此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{
size_t index = FREELIST_INDEX(bytes);
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);
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);
*my_free_list = next_obj = (obj *)(chunk + bytes);
//将取出的多余的空间加入到相应的free list里面去
for (int i = 1;; ++i){
current_obj = next_obj;
next_obj = (obj *)((char *)next_obj + bytes);
if (nobjs - 1 == i){
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{//内存池剩余空间连一个区块的大小都无法提供
size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
if (bytes_left > 0){
obj **my_free_list = free_list + FREELIST_INDEX(bytes_left);
((obj *)start_free)->next = *my_free_list;
*my_free_list = (obj *)start_free;
}
start_free = (char *)malloc(bytes_to_get);
if (!start_free){
obj **my_free_list = 0, *p = 0;
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);
}
}
}
alloc运行一瞥:
end_free - start_free 大小为战备池pool的大小
图片中的累计申请量RoundUp为上面代码中的heap_size

第一次申请时,申请大小为32bytes,3号链表未悬挂区块,向pool要求内存,pool大小为0,所以调用malloc申请一块20 * 2 * 32 bytes,留一半放入pool中,返回一块区块给客户.剩下的640bytes放入pool中(该次申请含有cookie)

第二次申请时,申请大小为64bytes,3号链表未悬挂区块,由于pool够切割出一块,直接返回一块给客户,剩余的挂到第7号链表(本次申请不含cookie),pool的容量清零。

第三次申请原理同第一次,该次申请含cookie。pool剩余2000

第四次申请原理同第二次,该次申请不含cookie。pool剩余240(pool最多切出20块,剩余的为本次的内存碎片,由下次分配处理)

第五次连续申请三次88bytes,由于链表悬挂了区块所以直接取出返回给客户,无cookie

第六次申请同第第四次,pool剩余80,不含cookie

第七次申请104bytes,12号链表未悬挂区块,pool不够切割出一块,则将pool放到匹配的链表上(pool大小为80,则将该区块放到第9条链表),再malloc申请内存,含cookie
系统内存分配完,malloc失败时措施





浙公网安备 33010602011771号