内存管理基本技术之:边界标记

来源:互联网 发布:淘宝好评返现要降权吗 编辑:程序博客网 时间:2024/05/22 06:31

内存管理基本技术之:边界标记

版权声明:
    本文章由vt.buxiu发布在
www.vtzone.org,版权归vtzone研究小组所有,转载请保持此声明!!!

@内容摘要:

边界标记用法最初有Knuth提出,在其计算机算法经典著作《计算机程序设计艺术》中有详细叙述。经常被应用在支持合并策略的内存分配器中。@
最容易想象到的原始的内存块:每一块保存一个块头和快尾,为了维护本块与前后快的关系,块头还保存了两个指针分别指向前一块和后一块。如下图:








块头的结构定义如下:



其实,头和尾的结构可以是一样的,也就是说块头中的指向前后块的那两个指针是可以省略的。下图省去了块头中前后两个指针,图中绿色的虚线表示,用于将来合并的两块物理是连续的,也就是说第一块的块尾后,紧急这是第一块的块头。





这样一来,头和尾的结构可以定义为:

 

struct TTailer{
     size_t size:
31;   //本块的大小
     size_t used:1;    //标记下一块是否被使用
}
;
typedef TTailer THeader;

由于边界标记常用于支持合并策略的分配器中,下面分别描述使用边界标记方法进行分割和合并的过程。
分割过程:
分割前,初始的一大块内存如下图,大小为2*sizeof(THeader)+size。分割后,分割为两个小块,其中第一块大小为sizeof(THeader)+size1,第二块大小为2*sizeof(THeader)+size2,当然分割过程必须保证分割前后的内存块总和是相等的:2*sizeof(THeader)+size = 2*sizeof(THeader)+size1 + 2*sizeof(THeader)+size2










初始的时候我们向系统申请一个size+2*sizeof(THeader)大小的内存。

 

 

//常量定义:4K
const size_t size = 4*1024;      

//向系统申请一块4k+2*sizeof(THeader)bytes内存
void* raw =  malloc(size+2*sizeof(THeader));

//初始化,设置该块内存的头和尾
THeader* header = (THeader*)raw;
header
->size = size;
header
->used = 0;
TTailer
* tailer = (TTailer*)(header + sizeof(THeader) + header->size);
tailer
->size = header->size;
tailer
->used = header->used;

 

分割的时候,将大块分割为两小块,其中一块返回给应用满足其分配请求,其中used标志设置为1,另一块保留下来以备满足后续的分配请求.

合并过程:合并的过程正好是分割的反操作,为了说明的完整性,下面给出合并的图例。







合并操作发生在当应用程序释放内存块的时候,先判断被free的块的前一块是否空闲,如果空闲修改前一块得size直接包含刚刚归还的块即可;然后再判断后一块是否空闲,如果空闲再修改前一块size,包含后一块的大小。
       合并操作代码:

 

 

 


void Merge(THeader* fisrt, THeader* second)
{
    
//如果合并的两块有一个不是空闲的,啥也不干
    asser(!first->used && !second->used);
    
if(!first->used || !second->used)
        
return;

    
//第一块块头    
    
//直接调整前一个空闲块的大小
    first->size += (second->size + 2*sizeof(CFirstFitHeader));
    
         
     
//第一块块尾
     TTailer* first_block_tailer =( TTailer*)(first + first->size + second->size + sizeof(THeader) + sizeof(TTailer))
     first_block_tailer
->size = first->size;
     
    
return;
}

 

释放操作,注意,这里为了简化描述,没有对是否存在前一块和后一块做校验,实际项目中必须做校验。



很多支持合并的分配器使用边界标记的方式,每一个内存块保留一块头和块尾,当请求的平均大小在10字节的时候,头与尾空间消耗将不能容忍,一个4字节的块头将消耗10%空间,尾也浪费10%空间。


 

 


void  Release(void* address)
{
    
//向后移动一个块头大小
    THeader* header = static_cast<THeader*>(address - sizeof(THeader));
     
//将该块设置为空闲,准备与前后空闲块进行合并
    header->used = 0;
     
     
//后一块是否空闲
     THeader* mext_block_header = (THeader*)(header + sizeof(THeader) + sizeof(TTailer) + header->size);
     
if(mext_block_header->used == 0)
     
{
         Merge(header, mext_block_header);
     }


     
//前一块是否空闲
     TTailer* prev_block_tailer = (TTailer*)(header - sizeof(TTailer));
     
if(prev_block_tailer->used == 0)
     
{
         THeader
* prev_block_header = (THeader*)(prev_block_tailer - sizeof(THeader) - sizeof(TTailer) - prev_block_tailer->size);
         Merge(prev_block_header, header);
     }


    
return;    
}

 

 


void* split(THeader* block, size_t nbytes)
{
     size_t oldsize 
= block->size;
     
if(block->size >= (2*sizeof(THeader) + nbytes)){//将被分割的块大于分割后的大小+块头+快尾
         
//从头部分割一块出去
         
//设置第一块头
         block->size = nbytes;                       
         block
->used = 1;
         
//设置第一块尾
         TTailer* first_block_tailer = (TTailer*)(block + sizeof(THeader) + nbytes);
         first_block_tailer
->size = nbytes;
         first_block_tailer
->used = 1;

         
//调整第二块
         
//设置第二块头
         THeader* second_block_header = (THeader*)(block + 2*sizeof(THeader) + nbytes);
        second_block_header
->size = oldsize - (2*sizeof(THeader) + nbytes);
        second_block_header
->used = 0;
         
//设置第二块尾
         TTailer* second_block_tailer = (TTailer*)(second_block_header + sizeof(THeader) + second_block_header->size);
        second_block_tailer
->size = second_block_header->size;
        second_block_tailer
->used = second_block_header->used;
         
return (void*)(block + sizeof(THeader));
     }


     
//内存块不够大,无法分割
    return NULL;
}

 

 

 

 

 

复制内容到剪贴板
struct TTailer{
     size_t size:
31;   //本块的大小
     size_t used:1;    //标记下一块是否被使用
}
;

struct THeader{
     size_t size:
31;   //本块的大小
     size_t used:1;    //标记上一块是否被使用
     TTailer * prev;   //指向前一个块的头
     THeader* next;    //指向下一个块的头
}
;
作者:vt.buxiu@www.vtzone.org