内存管理基本技术之:边界标记
Posted ouyangbuxiu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了内存管理基本技术之:边界标记相关的知识,希望对你有一定的参考价值。
内存管理基本技术之:边界标记
版权声明:
本文章由vt.buxiu发布在 www.vtzone.org,版权归vtzone研究小组所有,转载请保持此声明!!!
@内容摘要:
边界标记用法最初有Knuth提出,在其计算机算法经典著作《计算机程序设计艺术》中有详细叙述。经常被应用在支持合并策略的内存分配器中。@
最容易想象到的原始的内存块:每一块保存一个块头和快尾,为了维护本块与前后快的关系,块头还保存了两个指针分别指向前一块和后一块。如下图:
本文章由vt.buxiu发布在 www.vtzone.org,版权归vtzone研究小组所有,转载请保持此声明!!!
@内容摘要:
边界标记用法最初有Knuth提出,在其计算机算法经典著作《计算机程序设计艺术》中有详细叙述。经常被应用在支持合并策略的内存分配器中。@
最容易想象到的原始的内存块:每一块保存一个块头和快尾,为了维护本块与前后快的关系,块头还保存了两个指针分别指向前一块和后一块。如下图:
块头的结构定义如下:
其实,头和尾的结构可以是一样的,也就是说块头中的指向前后块的那两个指针是可以省略的。下图省去了块头中前后两个指针,图中绿色的虚线表示,用于将来合并的两块物理是连续的,也就是说第一块的块尾后,紧急这是第一块的块头。
这样一来,头和尾的结构可以定义为:
struct
TTailer
...
{
size_t size:31; //本块的大小
size_t used:1; //标记下一块是否被使用
} ;
typedef TTailer THeader;
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;
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; //指向下一个块的头
}
;
以上是关于内存管理基本技术之:边界标记的主要内容,如果未能解决你的问题,请参考以下文章