麻痹的不会在CSDN上贴图片,只好用文字来表达了
实际上,这个最简单的垃圾回收器的实现,我们只用关心,两个指针,以及一个结构,在我的代码中如下
static BYTE * m_begin = (BYTE * )HeaderSize;
static BYTE * m_end = (BYTE * )HeaderSize;
typedef struct MemBlock _MemBlock;
struct MemBlock
... {
_MemBlock* pPrev;
char buffer[BlockSize];
} ;
以及,为什么只有2个指针以及这一个结构,就可以完成GC的基本功能
一、初始化
由两个指针的初始化我们可以看出
#define HeaderSize (sizeof(void*))
如果在32位的环境中 两个值都是4,为什么这样?先不管他,从这里我们至少可以看出一个情况
m_end-m_begin == 0 ,也就是说 最开始的时候,用户这时候可使用的内存为0
可以先记住 m_end-m_begin 指的是GC内部这时候可交给用户使用的内存大小!
二、GC内存的申请
当用户第一调用 gc_malloc 函数时,那么一定要进行新的内存的申请
这时候应该申请多大呢?
这要看 BlockSize 的大小,当用户申请的内存大于我们定义的块大小时,那么为了能给用户一块连续的
内存,GC将按照用户的需要,重新申请足够的内存
// 来判断是使用默认块大小还是开辟足够的内存
if (cb >= BlockSize)
... {
pNewMemBlock = (_MemBlock*)malloc(HeaderSize + cb);
}
... {
// 这里开辟的内存的大小为_MemBlock
// 即 sizeof(_MemBlock *) + BlockSize
// 也等于 HeaderSize + BlockSize
// 则下面开辟内存也可以换成这句
// _MemBlock* pNew = (_MemBlock*)malloc(HeaderSize + BlockSize);
pNewMemBlock = (_MemBlock*)malloc(sizeof(_MemBlock));
}
之后,如果下一次用户申请内存时,GC内部有足够的内存给用户的话,将不再进行内存的申请,只需要移动m_end指针即可。
三、两个指针在做什么
m_begin = pNewMemBlock -> buffer;
第一次时,m_begin指向的是用户可用内存块的最开始的位置
之后呢?哈哈,其实之后也是这样,m_begin永远指向用户可使用的内存块的最开始的位置,并且m_begin-HeaderSize 这个地址(指针),指向了这个块(_MemBlock)这个数据结构的最开始的位置,也就是说我们可以通过m_begin得到每次申请的内存块的指针,也就是说 m_begin永远指向GC内部最后申请的内存块。
m_end
当GC内部不需要进行内存申请时,他始终在现有的内存块中进行移动,当GC内部进行内存申请时,m_end指向新内存块的尾部。并且无论GC内部申请了还是没申请内存,m_end始终指向一个内存地址,这个内存地址有足够的内存可以交给用户使用。
if (cb >= BlockSize)
m_end = pNewMemBlock -> buffer + cb;
else
m_end = pNewMemBlock -> buffer + BlockSize;
// 并将这一段内存交给用户使用
m_end -= cb;
return m_end;
四、两个指针怎么就能实现,所有的内存块的管理呢?
实际上这里使用的存储结构跟链表有点相似,但并不是典型的链表
typedef struct MemBlock _MemBlock;
typedef struct MemBlock
... {
_MemBlock* pPrev;
char buffer[BlockSize];
} ;
这个数据结构中 pPrev 的值的改变,是在下一次,GC内部进行内存申请时才被改变的
pNewMemBlock -> pPrev = pNowHeader;
// 重新设置m_begin指针,将 m_begin 指向新内存块的开始
m_begin = pNewMemBlock -> buffer;
总结:
用户在使用这个GC时,实际上始终在跟m_end打交道,m_end这个指针,提供给用户内存的访问能力
m_begin 这个指针 指向正在使用的内存快数据结构中 buffer 最开始
这个GC使用的数据结构是一个类似链表的数据结构,但对这个链表的操作,却比链表更简单。