💡 动态存储管理源码重构

This commit is contained in:
康建伟
2020-04-16 11:28:41 +08:00
parent c6abe2ff86
commit 81db65cd68
58 changed files with 5587 additions and 0 deletions

View File

@@ -0,0 +1,47 @@
#include <stdio.h>
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
int main(int argc, char* argv[]) {
Space pav;
Space p[12]; // 记录申请到的内存的指针
int s[12] = {10, 20, 30, 50, 5, 15, 10, 5, 15, 15, 2, 20}; // 申请的空间大小
int i = 0;
printf("████████ InitSpace \n");
{
int max = 200; // 初值建议为20的倍数目的是打印出来可以对齐
printf("初始化包含 %d 个\"\"的内存块后,当前内存布局为:\n", max);
pav = InitSpace(max);
PrintMemoryLayout();
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ AllocBoundTag \n");
{
for(i = 0; i < 12; i++) {
printf("████ %2d> 申请 %d 个\"\"的内存后,当前内存布局为:\n", i + 1, s[i]);
p[i] = AllocBoundTag(&pav, s[i]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
printf("████████ FreeBoundTag \n");
{
// 定义一个指针回收顺序
int a[10] = {7, 3, 10, 6, 8, 5, 11, 1, 0, 4};
for(i = 0; i < 10; i++) {
printf("回收 p%d 指向的内存...\n", a[i] + 1);
FreeBoundTag(&pav, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}

View File

@@ -0,0 +1,330 @@
/*==============
* 边界标识法
*
* 包含算法: 8.1
===============*/
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
/*
* 全局变量:永远指向初始空间的头部。
* 该变量有两个作用:
* 1.用来追踪内存布局,即查看内存的使用情况。
* 2.指示内存的起始和结尾
*/
static Space av;
// 记录空间容量,用于追踪内存布局
static int len;
/*
* 初始化一块总大小为n个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head和foot。
*/
Space InitSpace(int n) {
Space space, head, foot;
// 初始化空闲内存
space = (Space) malloc(n * sizeof(WORD));
if(space == NULL) {
return NULL;
}
// 初始化头部信息
head = space;
head->llink = space; // 前驱指向自身
head->rlink = space; // 后继指向自身
head->size = n; // 空间大小已经包含了head和foot
head->tag = 0; // 标记空间空闲
// 初始化底部信息
foot = FootLoc(head); // 初始化底部指针
foot->uplink = head; // 底部域链接到本结点的头部
foot->tag = 0; // 标记空间空闲
// 记下空间的起始位置和容量
av = space;
len = n;
return space;
}
/*
* ████████ 算法8.1 ████████
*
* 边界标识法的内存分配算法
*
* 从空间pav中申请一块大小至少为n的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
* 为了使分配后的剩余块尽量均匀分布每次分配完之后都要把空间指针pav向前移动具体描述参见教材。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.为了避免空间碎片化过快这里增加了一个容差e具体含义参考教材描述。
* 3.这里申请分配n个字的空间指的是已经完成换算的空间。
* 比如用户想要容量为10个字的空间但每个空间又包含head和foot这两个字存储空间使用信息
* 因此实际上分配的空间大小应为12个字这个"12"就是n的含义。
* 教材中提到"head和foot在分配时忽略不计",这样是为了伪码书写的方便。实际写代码时,这两个空间是不能忽略的。
* 但是如果按照上面的描述把n解释为已经换算后空间而不是用户原始申请的空间
* 那么既满足了没有忽略空间,又满足了在书面效果上,跟教材伪码统一,可谓两全其美。
*/
Space AllocBoundTag(Space* pav, int n) {
Space p, f;
/*
* 增加一个判断如果换算后的空间容量小于3则直接返回。
* 因为head和foot本身就占了2个字空间用户至少申请1个字的话总申请空间至少为3个字。
*/
if(n < 3) {
printf("日志:分配失败!申请的\"\"数应当不小于3\n");
return NULL;
}
// 查找不小于n的空闲块
for(p = *pav; p && p->size < n && p->rlink != *pav; p = p->rlink) {
}
// 找不到合适的空闲块,返回空指针
if(!p || p->size < n) {
return NULL;
}
/* 至此p指向了满足条件的空闲块 */
// 让f指向该空闲块的底部
f = FootLoc(p);
// pav指向p结点的后继结点即指向下一个空闲块
*pav = p->rlink;
// 如果空闲块比申请的容量大不了多少,则需要整块分配,即不保留<=e的剩余量
if(p->size - n <= e) {
// 由于上面已经让pav指向了下一个空闲块所以如果pav与p相等说明此时只有一个空闲块了(注意空闲块链表是双循环的)
if(*pav == p) {
*pav = NULL; // 如果仅剩一个空闲块了,那么被占用后,可利用空间表变为空表
// 否则,在表中删除分配的结点
} else {
(*pav)->llink = p->llink; // 修改pav的前驱
p->llink->rlink = *pav; // 修改pav前驱的后继
/* 在上述操作中p结点的前驱与后继并没有改变这是为了方便将来的回收操作 */
}
// 更新占用块为占用状态
p->tag = f->tag = 1;
printf("日志:分配成功!申请 %d 个\"\",实际分配了 %d 个\"\"。空闲空间容量为 %d ...\n", n, n + e, AvailableSpace(*pav));
// 如果空闲块很大则从中间切割占用后面的n个字
} else {
f->tag = 1; // 修改分配块的底部标志,表示其处于占用状态
p->size -= n; // 设置剩余块大小
f = FootLoc(p); // 计算剩余块的新底部位置
f->tag = 0; // 设置新底部标志为空闲
f->uplink = p; // 新底部依然指向空闲块头部
p = f + 1; // 计算分配块的新头部位置
p->tag = 1; // 设置分配块新头部标志为占用
p->size = n; // 设置分配块的容量
// 修改分配块底部的链接(教材中缺失了此步骤)
(FootLoc(p))->uplink = p;
printf("日志:分配成功!申请并分配了 %d 个\"\"。空闲空间容量为 %d ...\n", n, AvailableSpace(*pav));
}
// 返回分配块首地址
return p;
}
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注:以下描述中的"释放块"就是指针p指向的内存
*/
void FreeBoundTag(Space* pav, Space p) {
Space h, f, q;
int Ltag, Rtag;
if(p == NULL) {
printf("日志:回收失败!内存指针为空。空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
Ltag = p == av ? 1 : (p - 1)->tag; // 块p的左邻区标志。特别注意如果块p位于内存起始处则认为它的左邻区为占用。
Rtag = (p + p->size) == (av + len) ? 1 : (p + p->size)->tag; // 块p的右邻区标志。特别注意如果块p位于内存结尾处则认为它的右邻区为占用。
/*
* 1.释放块的左、右邻区均为占用块
*
* 此时仅需要将块p插入到pav所指结点的之前即可(当然,插入到之后也是可以的)
*/
if(Ltag == 1 && Rtag == 1) {
printf("日志:\"释放块\"的容量为 %d ,且它的左、右邻区均为占用块...\n", p->size);
f = FootLoc(p);
f->uplink = p;
f->tag = 0;
p->tag = 0;
// 空闲链表为空时直接将块p变为新的独立的空闲块
if((*pav) == NULL) {
*pav = p->llink = p->rlink = p;
// 否则将块p插入到pav之前
} else {
q = (*pav)->llink;
p->rlink = *pav;
p->llink = q;
q->rlink = (*pav)->llink = p;
// 令刚释放的结点成为下次分配空间时最先查找的结点
*pav = p;
}
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 2.释放块的左邻区为空闲块,右邻区为占用块
*
* 此时需要合并左邻区与释放块
*/
if(Ltag == 0 && Rtag == 1) {
printf("日志:\"释放块\"的容量为 %d ,且它的左邻区为空闲块,右邻区为占用块...\n", p->size);
h = (p - 1)->uplink; // 左邻区的头部,这将成为合并后的新块的头部
h->size += p->size; // 左邻区容量增大
f = FootLoc(p); // 将释放块的底部做为合并后的新块的底部
f->uplink = h;
f->tag = 0;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 3.释放块的左邻区为占用块,右邻区为空闲块
*
* 此时需要合并释放块与右邻区
*/
if(Ltag == 1 && Rtag == 0) {
printf("日志:\"释放块\"的容量为 %d ,且它的左邻区为占用块,右邻区为空闲块...\n", p->size);
h = p + p->size; // 右邻区的头部
f = FootLoc(h); // 右邻区的底部,这将成为合并后的新块的底部
f->uplink = p; // 释放块的头部将作为合并后新块的头部
p->tag = 0;
p->size += h->size;
// 释放块的头部链接域要更新为与右邻区头部的链接域一致
p->llink = h->llink;
p->rlink = h->rlink;
h->llink->rlink = p;
h->rlink->llink = p;
// pav指向合并后的结点的新头部
*pav = p;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 4.释放块的左、右邻区均为空闲块
*
* 此时需要合并左邻区、释放块、右邻区
*/
if(Ltag == 0 && Rtag == 0) {
printf("日志:\"释放块\"的容量为 %d ,且它的左、右邻区均为空闲块...\n", p->size);
h = (p - 1)->uplink; // 左邻区的头部,这将成为合并后的新块的头部
q = p + p->size; // 右邻区的头部
f = FootLoc(q); // 右邻区的底部,这将成为合并后的新块的底部
h->size += p->size + q->size; // 合并后的新块大小
f->uplink = h; // 新块底部信息也要更新
// 移除右邻区
q->rlink->llink = q->llink;
q->llink->rlink = q->rlink;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
}
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout() {
Space p;
int count;
int i;
p = av;
count = av->size;
for(i = 1; i <= count; i++) {
if(p->tag == 0) {
printf("");
} else {
printf("");
}
if(i == count && count < len) {
p = p + p->size;
count += p->size;
printf("|");
} else {
printf(" ");
}
// 每隔20个换一下行
if(i % 20 == 0) {
printf("\n");
}
}
if(len % 20 != 0) {
printf("\n");
}
}
/*
* 计算可用的空闲空间容量
*
* 注:仅限内部使用,用在日志打印中
*/
static int AvailableSpace(Space pav) {
Space p;
int count;
if(pav == NULL) {
return 0;
}
p = pav;
count = 0;
do {
count += p->size;
p = p->rlink;
} while(p != pav);
return count;
}

View File

@@ -0,0 +1,96 @@
/*==============
* 边界标识法
*
* 包含算法: 8.1
===============*/
#ifndef BOUNDARYTAGMETHOD_H
#define BOUNDARYTAGMETHOD_H
#include <stdio.h>
#include <stdlib.h>
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define e 5 // 分配空间时用到的容差
#define FootLoc(p) p+(p->size)-1 // 将p指针定位到p所指内存块的底部
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
/*
* 注:
* 教材中将llink和uplink封装在了一个联合体中个人感觉这个封装有些鸡肋。
* 一方面head和foot的公共部分其实只有tag如果真想用联合体那么应当把size和rlink也封装起来。
* 另一方面,这个封装节省的空间很有限,而且直接影响了代码的可读性。
* 这里只是教学代码,而不是真实的系统代码,所以空间考虑在其次,原理展示为首要任务。
* 此外,教材中的伪码中也并没有考虑这个联合体,而是直接进行操作的。
* 综上所述,这里去除了教材中的联合体结构。
* 如想观察带有联合体的写法可以参考CFree分支的代码。
*/
int tag; // 块标志0空闲1占用头部和尾部均有
struct WORD* llink; // 头部域,指向前驱结点
struct WORD* rlink; // 头部域,指向后继结点
int size; // 头部域,块大小
struct WORD* uplink; // 底部域,指向本结点头部
} WORD;
typedef WORD* Space; // Space指向可利用空间的指针类型
/*
* 初始化一块大小为n个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head和foot。
*/
Space InitSpace(int n);
/*
* ████████ 算法8.1 ████████
*
* 边界标识法的内存分配算法
*
* 从空间pav中申请一块大小至少为n的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
* 为了使分配后的剩余块尽量均匀分布每次分配完之后都要把空间指针pav向前移动具体描述参见教材。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.为了避免空间碎片化过快这里增加了一个容差e具体含义参考教材描述。
* 3.这里申请分配n个字的空间指的是已经完成换算的空间。
* 比如用户想要容量为10个字的空间但每个空间又包含head和foot这两个字存储空间使用信息
* 因此实际上分配的空间大小应为12个字这个"12"就是n的含义。
* 教材中提到"head和foot在分配时忽略不计",这样是为了伪码书写的方便。实际写代码时,这两个空间是不能忽略的。
* 但是如果按照上面的描述把n解释为已经换算后空间而不是用户原始申请的空间
* 那么既满足了没有忽略空间,又满足了在书面效果上,跟教材伪码统一,可谓两全其美。
*/
Space AllocBoundTag(Space* pav, int n);
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void FreeBoundTag(Space* pav, Space p);
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout();
/*
* 计算可用的空闲空间容量
*
* 注:仅限内部使用,用在日志打印中
*/
static int AvailableSpace(Space pav);
#endif

View File

@@ -0,0 +1,12 @@
# 包含公共库
include_directories(${CMAKE_SOURCE_DIR}/Status)
# 生成可执行文件
add_executable(BoundaryTagMethod BoundaryTagMethod.h BoundaryTagMethod.c BoundaryTagMethod-main.c)
# 链接公共库
target_link_libraries(BoundaryTagMethod Scanf_lib)
# 记录要拷贝到*.exe目录下的资源文件
file(GLOB TestData TestData*.txt)
# 将资源文件拷贝到*.exe目录下不然无法加载
file(COPY ${TestData} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,44 @@
#include <stdio.h>
#include "BuddySystem.h" //**▲08 动态存储管理**//
int main(int argc, char* argv[]) {
FreeList avail;
WORD* p[8]; // 记录申请到的内存的指针
int s[8] = {4, 5, 6, 7, 1, 5, 3, 9}; // 申请的空间大小
int i;
printf("████████ InitSpace \n");
{
printf("初始化一个内存块...\n");
InitSpace(avail);
PrintMemoryLayout();
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ AllocBuddy \n");
{
for(i = 0; i < 8; i++) {
printf("申请大小为 %d 个字的内存块...\n", s[i]);
p[i] = AllocBuddy(avail, s[i]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
printf("████████ FreeBuddy \n");
{
// 定义一个指针回收顺序
int a[8] = {2, 0, 5, 7, 1, 4, 3, 6};
for(i = 0; i < 8; i++) {
FreeBuddy(avail, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}

View File

@@ -0,0 +1,283 @@
/*==============
* 伙伴系统
*
* 包含算法: 8.2
===============*/
#include "BuddySystem.h"
// 记录内存的起始地址,在计算伙伴块时候需要用到
WORD* start;
/*
* 初始化一块大小为2^M个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head。
*/
void InitSpace(FreeList avail) {
int k;
WORD* r;
// 遍历M+1个元素
for(k = 0; k <= M; k++) {
avail[k].nodesize = (int) pow(2, k);
avail[k].first = NULL;
}
r = (WORD*) malloc((int) pow(2, M) * sizeof(WORD));
if(r == NULL) {
exit(OVERFLOW);
}
// 设置头部信息
r->llink = r->rlink = r;
r->tag = 0;
r->kval = M;
avail[M].first = r;
start = r;
}
/*
* ████████ 算法8.2 ████████
*
* 伙伴系统的内存分配算法
*
* 从空间avail中申请一块大小至少为n(原始值)的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.这里申请分配n个字的空间指的是用户申请的原始空间。
* 实际在申请时还要考虑到每个块前面有1个字的head信息即经过换算后实际需要申请(n+1)个字。
* 这里的n与算法8.1里面的n含义正好相反需要注意。
*/
WORD* AllocBuddy(FreeList avail, int n) {
int k, i;
WORD* pa, * pre, * suc, * pi;
/*
* 增加一个判断如果换算后的空间容量小于1则直接返回。
*/
if(n < 1) {
printf("日志:分配失败!申请的\"\"数应当不小于1\n");
return NULL;
}
// 查找不小于n的空闲块
for(k = 0; k <= M && (avail[k].nodesize < n + 1 || !avail[k].first); k++) {
}
// 找不到合适的空闲块,返回空指针
if(k > M) {
printf("日志:分配失败!没有足够的空闲块\n");
return NULL;
}
pa = avail[k].first; // 指向可分配子表的第一个结点
pre = pa->llink; // 分别记下前驱和后继
suc = pa->rlink;
// 如果此处仅有一个空闲块,则分配后该子表变为空
if(pa == suc) {
avail[k].first = NULL;
// 否则,从链表头部摘下一个可用的空闲块,并将子表头指针指向下一个空闲块
} else {
pre->rlink = suc;
suc->llink = pre;
avail[k].first = suc;
}
/*
* 从k-1处开始逆向遍历FreeList数组向其中填充剩余的空闲块。
* 剩余的空闲块是对剩余可用空间的拆分。
*
* 这里用到一个公式2^m-2^n = 2^n+2^(n+1)+...+2^(m-1)
* 比如初始容量为2^16此时总共申请1500个字那么需要分配一块2^11的空闲块给它。
* 分配完之后剩余的容量为2^16-2^11 = 2^11+2^12+2^13+2^14+2^15。
* 这些剩余容量可以拆分为5个空闲块分别存储到15、14、13、12、11这五个索引处。
*/
for(i = 1; k - i >= 0 && avail[k - i].nodesize >= n + 1; i++) {
pi = pa + (int) pow(2, k - i); // 每次将pi指向剩余空间的后一半
pi->rlink = pi->llink = pi; // 初始化pi的前驱和后继
pi->tag = 0; // 标记为空闲块
pi->kval = k - i; // 设置该块的容量标志真实容量为2^(k-i)
avail[k - i].first = pi;
/*
* 注:
* 上面分解出来的pi直接添加到了avail中并没有考虑同位置处会不会有别的容量相同的空闲块。
* 这里不需要考虑的原因是如果同位置处已经存在别的容量相同的空闲块,
* 那么这里根本不需要分解剩余空间,换句话说,连这个循环都进不来。
* 只要进来这个循环,说明目标位置处已经为空了,没有找到合适的空闲块,所以这才进一步向高游标处寻找空闲块。
*/
}
// 最后剩下的最靠前的空间就是需要分配的空间(这里没有设置pa的前驱和后继因为没必要)
pa->tag = 1;
pa->kval = k - (--i);
printf("日志:分配成功!用户申请 %d 个字,系统申请 %d 个字,实际分配 %d 个字\n", n, n + 1, (int) pow(2, pa->kval));
return pa;
}
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void FreeBuddy(FreeList avail, WORD* p) {
int k;
WORD* r;
WORD* buddy = Buddy(p);
if(p == NULL) {
return;
}
/*
* 这里将p结点简单地插入到avail中包含三种情形
* 1.伙伴块非空闲
* 2.伙伴空闲但是伙伴的大小跟p的大小不一致说明伙伴还没拼合好
* 3.p拼接成了最后一个最大的空闲块
*/
if(buddy->tag == 1 || buddy->kval != p->kval || p->kval == M) {
for(k = 0; k <= M && k < p->kval; k++) {
// 查找p结点应当进入的插槽
}
// 找到插槽,采用头插法将空闲块插入到目标插槽
if(k <= M && k == p->kval) {
p->tag = 0;
if(avail[k].first == NULL) {
p->llink = p->rlink = p;
} else {
p->llink = avail[k].first->llink;
p->rlink = avail[k].first;
p->llink->rlink = p;
p->rlink->llink = p;
}
avail[k].first = p;
printf("日志:回收成功![%d, (2^%d)]进入插槽 %d 的空闲块链表上\n", (int) (p - start), k, k);
}
// 如果伙伴块是空闲的,此时应当进行合并操作
} else {
for(k = 0; k <= M && k < p->kval; k++) {
// 查找伙伴块所在的插槽
}
// 找到插槽,将伙伴块从空闲块链表中摘下来
if(k <= M && k == p->kval) {
// 伙伴在链表第一个位置
if(avail[k].first == buddy) {
buddy->rlink->llink = buddy->llink;
buddy->llink->rlink = buddy->rlink;
avail[k].first = buddy->rlink;
// 伙伴在中间位置
} else {
for(r = avail[k].first; r->rlink != buddy; r = r->rlink) {
// 查找伙伴r指向伙伴的前驱
}
r->rlink = buddy->rlink;
buddy->rlink->llink = r;
}
printf("日志:合并成功![%d, (2^%d)]和[%d, (2^%d)]合并成了", (int) (p - start), k, (int) (buddy - start), k);
// 合并之前,需要确定哪个伙伴靠前
if(p < buddy) {
p->tag = 0;
} else {
p = buddy;
}
p->kval = k + 1; // 指数增一后,即完成合并
printf("[%d, (2^%d)]\n", (int) (p - start), k + 1);
// 出现新的空闲块之后,要进入递归,查看该空闲块是否也存在空闲伙伴
FreeBuddy(avail, p);
}
}
}
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout() {
int i, count, total;
WORD* p;
printf("|");
p = start;
count = (int) pow(2, p->kval);
for(i = 1; i <= count; i++) {
if(p->tag == 0) {
printf("_");
} else {
printf("*");
}
// 进入到下一个块
if(i == count && count < (int) pow(2, M)) {
p = start + count;
count += (int) pow(2, p->kval);
printf("|");
}
}
printf("|\n");
}
/*
* 查找块p的伙伴
*
* 将一个空闲块对半分裂后,会生成的两个小空闲块,这两个小空闲块互为伙伴。
*
* 计算伙伴的算法为:
* 对于起始地址为p大小为2^k的内存块
* 1.若 p MOD 2^(k+1) == 0 ,则p的伙伴块的起始地址为p+2^k
* 2.若 p MOD 2^(k+1) == 2^k ,则p的伙伴块的起始地址为p-2^k。
*
* 注:仅限内部使用,用在回收算法中
*/
static WORD* Buddy(WORD* p) {
long s, m, n;
if(p == NULL) {
return NULL;
}
// start是整个空闲块的绝对起始地址s是p在伙伴系统中的绝对地址从0开始
s = p - start;
if(s < 0) {
return NULL;
}
m = (long) pow(2, p->kval);
n = (long) pow(2, p->kval + 1);
if(s % n == 0) {
return p + m;
}
if(s % n == m) {
return p - m;
}
return NULL;
}

View File

@@ -0,0 +1,89 @@
/*==============
* 伙伴系统
*
* 包含算法: 8.2
===============*/
#ifndef BUDDYSYSTEM_H
#define BUDDYSYSTEM_H
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define M 6 // 假设总空间大小为2^M个字子表个数为M+1
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
struct WORD* llink; // 头部域的前驱指针
struct WORD* rlink; // 头部域的后继指针
int tag; // 头部域的块标志0:空闲1:占用
int kval; // 指示块的大小比如其值为K时表示该块的大小为2^K
} WORD;
// 表头向量类型
typedef struct HeadNode {
int nodesize; // 该链表的空闲块的大小
WORD* first; // 该链表的表头指针
} FreeList[M + 1];
/*
* 初始化一块大小为2^M个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head。
*/
void InitSpace(FreeList avail);
/*
* ████████ 算法8.2 ████████
*
* 伙伴系统的内存分配算法
*
* 从空间avail中申请一块大小至少为n(原始值)的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.这里申请分配n个字的空间指的是用户申请的原始空间。
* 实际在申请时还要考虑到每个块前面有1个字的head信息即经过换算后实际需要申请(n+1)个字。
* 这里的n与算法8.1里面的n含义正好相反需要注意。
*/
WORD* AllocBuddy(FreeList avail, int n);
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void FreeBuddy(FreeList avail, WORD* p);
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout();
/*
* 查找块p的伙伴
*
* 将一个空闲块对半分裂后,会生成的两个小空闲块,这两个小空闲块互为伙伴。
*
* 计算伙伴的算法为:
* 对于起始地址为p大小为2^k的内存块
* 1.若 p MOD 2^(k+1) == 0 ,则p的伙伴块的起始地址为p+2^k
* 2.若 p MOD 2^(k+1) == 2^k ,则p的伙伴块的起始地址为p-2^k。
*
* 注:仅限内部使用,用在回收算法中
*/
static WORD* Buddy(WORD* p);
#endif

View File

@@ -0,0 +1,12 @@
# 包含公共库
include_directories(${CMAKE_SOURCE_DIR}/Status)
# 生成可执行文件
add_executable(BuddySystem BuddySystem.h BuddySystem.c BuddySystem-main.c)
# 链接公共库
target_link_libraries(BuddySystem Scanf_lib)
# 记录要拷贝到*.exe目录下的资源文件
file(GLOB TestData TestData*.txt)
# 将资源文件拷贝到*.exe目录下不然无法加载
file(COPY ${TestData} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,12 @@
# 包含公共库
include_directories(${CMAKE_SOURCE_DIR}/Status)
# 生成可执行文件
add_executable(GarbageCollection SString.h SString.c GList-HT.h GList-HT.c GarbageCollection.h GarbageCollection.c GarbageCollection-main.c)
# 链接公共库
target_link_libraries(GarbageCollection Scanf_lib)
# 记录要拷贝到*.exe目录下的资源文件
file(GLOB TestData TestData*.txt)
# 将资源文件拷贝到*.exe目录下不然无法加载
file(COPY ${TestData} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,201 @@
/*============================
* 广义表的头尾链表存储表示
*
* 包含算法: 5.5、5.6、5.7、5.8
=============================*/
#include "GList-HT.h" //**▲05 数组和广义表**//
/*
* 初始化
*
* 初始化空的广义表长度为0深度为1。
*
*【注】
* 需要对每一层去掉括号考察
*/
Status InitGList(GList* L) {
if(L == NULL) {
return ERROR;
}
*L = NULL;
return OK;
}
/*
* ████████ 算法5.7 ████████
*
* 创建
*
* 由字符串S创建广义表L。
*/
Status CreateGList(GList* L, SString S) {
SString emp; // 代表空广义表的字符串
SString hsub, sub;
GList p, q;
if(L == NULL) {
return ERROR;
}
// 清理字符串S中的空白包括清理不可打印字符和清理空格
ClearBlank(S);
if(StrEmpty(S)) {
return ERROR;
}
StrAssign(emp, "()");
/*
* 如果输入串为(),则代表需要创建空的广义表
*
*【注】
* 教材这里的代码是有问题的。
* StrCompare的返回值指示的是两个字符串的大小而不是指示两个字符串是否相等。
* 如果给定的S与()相等返回值应当是0。
*/
if(!StrCompare(S, emp)) {
*L = NULL;
} else {
*L = (GList) malloc(sizeof(GLNode));
if(*L == NULL) {
exit(OVERFLOW);
}
// 初始化标记为0意思是该结点未访问
(*L)->mark = 0;
// 创建原子
if(StrLength(S) == 1) {
(*L)->tag = Atom;
(*L)->Node.atom = S[1];
} else {
(*L)->tag = List;
p = *L;
// 去掉最外层括号
SubString(sub, S, 2, StrLength(S) - 2);
// 重复建n个子表
do {
// 从sub中分离出表头串hsub分离完成后sub也会发生变化
sever(hsub, sub);
// 递归创建广义表
CreateGList(&(p->Node.ptr.hp), hsub);
q = p;
// 如果表尾不为空,需要继续处理表尾
if(!StrEmpty(sub)) {
p = (GList) malloc(sizeof(GLNode));
if(p == NULL) {
exit(OVERFLOW);
}
// 初始化标记为0意思是该结点未访问
p->mark = 0;
p->tag = List;
q->Node.ptr.tp = p;
}
} while(!StrEmpty(sub));
q->Node.ptr.tp = NULL;
}
}
return OK;
}
/*
* 图形化输出
*
* 带括号输出广义表L。
*/
void PrintGList(GList L) {
Print(L, Head);
printf("\n");
}
/*
* 图形化输出的内部实现mark是图形化输出标记。
*/
static void Print(GList L, Mark mark) {
// L为空
if(L == NULL) {
if(mark == Head) {
printf("(");
}
printf(")");
// L不为空时
} else {
// 对于原子结点,输出原子
if(L->tag == Atom) {
printf("%c", L->Node.atom);
// 对于子表结点,要对表头、表尾分别讨论
} else {
if(mark == Head) {
printf("(");
} else {
printf(",");
}
Print(L->Node.ptr.hp, Head);
Print(L->Node.ptr.tp, Tail);
}
}
}
/*
* ████████ 算法5.8 ████████
*
* 将非空串str分割成两部分hsub为第一个','之前的子串str为第一个','之后的子串。
*
*【注】
* 1.这里假设字符串str输入正确其中无空白符号且str【已经脱去最外层括号】。
* 2.分离完成后str也会发生变化
*/
static void sever(SString hstr, SString str) {
int i, k, n;
SString ch;
n = StrLength(str);
i = 0; // 遍历字符串时的游标
k = 0; // 标记遇到的未配对括号数量
do {
++i;
// 截取str第一个字符
SubString(ch, str, i, 1);
if(ch[1] == '(') {
++k;
}
if(ch[1] == ')') {
--k;
}
} while(i < n && (ch[1] != ',' || k != 0));
// 如果存在多个广义表结点
if(i < n) {
SubString(hstr, str, 1, i - 1);
SubString(str, str, i + 1, n - i);
// 只有一个广义表结点
} else {
StrCopy(hstr, str);
ClearString(str);
}
}

View File

@@ -0,0 +1,96 @@
/*============================
* 广义表的头尾链表存储表示
*
* 包含算法: 5.5、5.6、5.7、5.8
=============================*/
#ifndef GLIST_HT_H
#define GLIST_HT_H
#include <stdio.h>
#include <stdlib.h> // 提供 malloc、realloc、free、exit 原型
#include "Status.h" //**▲01 绪论**//
#include "SString.h" //**▲04 串**//
/* 原子元素类型 */
typedef char AtomType;
/*
* 广义表结点标记
*
* Atom-0原子结点
* List-1表结点
*/
typedef enum { Atom, List } ElemTag;
/* 广义表(头尾链表存储表示)类型定义 */
typedef struct GLNode {
int mark; // 为第8章第5节新增的变量用来给广义表打上遍历标记会在创建中初始化为0
ElemTag tag; // 公共标记,用于区分原子结点和表结点
// 原子结点和表结点的联合部分
union {
AtomType atom; // atom是原子结点的值域AtomType由用户定义
struct {
struct GLNode* hp; // 指向表头
struct GLNode* tp; // 指向表尾
} ptr; // 表结点的指针域
} Node;
} GLNode;
/* 广义表类型 */
typedef GLNode* GList;
/*
* 图形化输出标记
*
* Head代表广义表指针来自表头
* Tail代表广义表指针来自表尾
*/
typedef enum { Head, Tail } Mark;
/*
* 初始化
*
* 初始化空的广义表长度为0深度为1。
*
*【注】
* 需要对每一层去掉括号考察
*/
Status InitGList(GList* L);
/*
* ████████ 算法5.7 ████████
*
* 创建
*
* 由字符串S创建广义表L。
*/
Status CreateGList(GList* L, SString S);
/*
* 图形化输出
*
* 带括号输出广义表L。
*/
void PrintGList(GList L);
/*
* 图形化输出的内部实现mark是图形化输出标记。
*/
static void Print(GList L, Mark mark);
/*
* ████████ 算法5.8 ████████
*
* 将非空串str分割成两部分hsub为第一个','之前的子串str为第一个','之后的子串。
*
*【注】
* 1.这里假设字符串str输入正确其中无空白符号且str【已经脱去最外层括号】。
* 2.分离完成后str也会发生变化
*/
static void sever(SString hstr, SString str);
#endif

View File

@@ -0,0 +1,67 @@
#include <stdio.h>
#include "GarbageCollection.h" //**▲08 动态存储管理**//
// 遍历广义表
void Traverse(GList L, void(Visit)(GList));
// 打印广义表结点信息
void PrintInfo(GList L);
int main(int argc, char* argv[]) {
GList G;
printf("创建并输出广义表:");
{
SString S;
char* s = "((),(e),(a,(b,c,d)))";
InitGList(&G);
StrAssign(S, s);
CreateGList(&G, S);
PrintGList(G);
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ MakeList \n");
{
printf("访问前的标志状态...\n");
Traverse(G, PrintInfo);
printf("\n");
MakeList(G);
printf("访问后的标志状态...\n");
Traverse(G, PrintInfo);
printf("\n");
}
PressEnterToContinue(debug);
return 0;
}
// 遍历广义表
void Traverse(GList L, void(Visit)(GList)) {
if(L == NULL) {
return;
}
Visit(L);
if(L->tag == List) {
Traverse(L->Node.ptr.hp, Visit);
Traverse(L->Node.ptr.tp, Visit);
}
}
// 打印广义表结点信息
void PrintInfo(GList L) {
if(L->tag == Atom) {
printf("mark = %d 原子结点:%c\n", L->mark, L->Node.atom);
} else {
printf("mark = %d 表结点\n", L->mark);
}
}

View File

@@ -0,0 +1,78 @@
/*==============
* 无用单元收集
*
* 包含算法: 8.3
===============*/
#include "GarbageCollection.h"
/*
* ████████ 算法8.3 ████████
*
* 遍历广义表(不使用栈)。
*/
void MakeList(GList G) {
GList t, p, q;
Status finished;
if(!G || G->mark != 0) {
return;
}
t = NULL; // t为p的母表
p = G;
finished = FALSE;
while(!finished) {
while(p->mark == 0) {
p->mark = 1; // MakeHead(p)的细化
q = p->Node.ptr.hp; // q指向p的表头
if(q != NULL && q->mark == 0) {
// 表头为原子结点
if(q->tag == Atom) {
q->mark = 1;
// 继续遍历子表
} else {
p->Node.ptr.hp = t;
p->tag = Atom;
t = p;
p = q;
}
}
} // 完成对表头的标记
q = p->Node.ptr.tp;
// 继续遍历表尾
if(q != NULL && q->mark == 0) {
p->Node.ptr.tp = t;
t = p;
p = q;
// BackTrack(finished)的细化
} else {
// 表结点,从表尾回溯
while(t && t->tag == List) {
q = t;
t = q->Node.ptr.tp;
q->Node.ptr.tp = p;
p = q;
}
// 结束
if(t == NULL) {
finished = TRUE;
// 从表头回溯
} else {
q = t;
t = q->Node.ptr.hp;
q->Node.ptr.hp = p;
p = q;
p->tag = List;
}// 继续遍历表尾
}
}
}

View File

@@ -0,0 +1,21 @@
/*==============
* 无用单元收集
*
* 包含算法: 8.3
===============*/
#ifndef GARBAGECOLLECTION_H
#define GARBAGECOLLECTION_H
#include <stdio.h>
#include "Status.h" //**▲01 绪论**//
#include "GList-HT.h" //**▲05 数组和广义表**//
/*
* ████████ 算法8.3 ████████
*
* 遍历广义表(不使用栈)。
*/
void MakeList(GList G);
#endif

View File

@@ -0,0 +1,167 @@
/*=============================
* 串的定长顺序存储表示(顺序串)
*
* 包含算法: 4.1、4.2、4.3、4.5
==============================*/
#include "SString.h" //**▲04 串**//
/*
* 初始化
*
* 构造一个值为chars的串T。
*
*【注】
* 该操作属于最小操作子集
*/
Status StrAssign(SString T, const char* chars) {
int i, len;
len = (int) strlen(chars);
// chars过长
if(len > MAXSTRLEN) {
return ERROR;
}
T[0] = len;
for(i = 1; i <= len; i++) {
T[i] = chars[i - 1];
}
return OK;
}
/*
* 清空
*
* 将串S清空。
*/
Status ClearString(SString S) {
// 只需要将长度置为0就可以
S[0] = 0;
return OK;
}
/*
* 判空
*
* 判断串S中是否包含有效数据。
*
* 返回值:
* TRUE : 串S为空
* FALSE: 串S不为空
*/
Status StrEmpty(SString S) {
return S[0] == 0 ? TRUE : FALSE;
}
/*
* 计数
*
* 返回串S中元素的个数。
*
*【注】
* 该操作属于最小操作子集
*/
int StrLength(SString S) {
return S[0];
}
/*
* ████████ 算法4.3 ████████
*
* 求子串
*
* 用Sub返回S[pos, pos+len-1]。
* 返回值指示是否截取成功。
*
*【注】
* 该操作属于最小操作子集
*/
Status SubString(SString Sub, SString S, int pos, int len) {
int i;
if(pos < 1 || pos > S[0] || len < 0 || pos + len - 1 > S[0]) {
return ERROR;
}
// 复制元素
for(i = 1; i <= len; i++) {
Sub[i] = S[pos + i - 1];
}
// 确定新长度
Sub[0] = len;
return OK;
}
/*
* 比较
*
* 比较串S和串T返回比较结果。
*
*【注】
* 该操作属于最小操作子集
*/
int StrCompare(SString S, SString T) {
int i = 1;
while(i <= S[0] && i <= T[0]) {
// 遇到不同的字符时,比较其大小
if(S[i] != T[i]) {
return S[i] - T[i];
}
i++;
}
return S[0] - T[0];
}
/*
* 复制
*
* 将串S复制到串T。
*/
Status StrCopy(SString T, SString S) {
int i;
// 连同长度信息一起复制
for(i = 0; i <= S[0]; i++) {
T[i] = S[i];
}
return OK;
}
/*
* 清理字符串S中的空白包括清理不可打印字符和清理空格。
*
*【注】
* 该函数是在本章中新增的。
*/
Status ClearBlank(SString S) {
int i, j;
// 如果是空串
if(S[0] == 0) {
return ERROR;
}
for(i = 1, j = 0; i <= S[0]; i++) {
// 如果遇到空白,则略过
if(S[i] == ' ' || !isprint(S[i])) {
continue;
}
j++;
S[j] = S[i];
}
S[0] = j;
return OK;
}

View File

@@ -0,0 +1,110 @@
/*=============================
* 串的定长顺序存储表示(顺序串)
*
* 包含算法: 4.1、4.2、4.3、4.5
==============================*/
#ifndef SSTRING_H
#define SSTRING_H
#include <stdio.h>
#include <string.h> // 提供 strlen 原型
#include <ctype.h> // 提供 isprint 原型
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define MAXSTRLEN 255 // 顺序串的最大串长
/*
* 串的顺序存储类型定义
*
* 注有效元素从SString的1号单元开始存储
* SString的0号单元用来存储其长度
*/
typedef unsigned char SString[MAXSTRLEN + 1]; // 0号单元存放串的长度
/*
* ████ 提示 ████
*
* 遵循教材的书写习惯pos指示字符的位序(不是索引)从1开始计数
*/
/*
* 初始化
*
* 构造一个值为chars的串T。
*
*【注】
* 该操作属于最小操作子集
*/
Status StrAssign(SString T, const char* chars);
/*
* 清空
*
* 将串S清空。
*/
Status ClearString(SString S);
/*
* 判空
*
* 判断串S中是否包含有效数据。
*
* 返回值:
* TRUE : 串S为空
* FALSE: 串S不为空
*/
Status StrEmpty(SString S);
/*
* 计数
*
* 返回串S中元素的个数。
*
*【注】
* 该操作属于最小操作子集
*/
int StrLength(SString S);
/*
* ████████ 算法4.3 ████████
*
* 求子串
*
* 用Sub返回S[pos, pos+len-1]。
* 返回值指示是否截取成功。
*
*【注】
* 该操作属于最小操作子集
*/
Status SubString(SString Sub, SString S, int pos, int len);
/*
* 比较
*
* 比较串S和串T返回比较结果。
*
*【注】
* 该操作属于最小操作子集
*/
int StrCompare(SString S, SString T);
/*
* 复制
*
* 将串S复制到串T。
*/
Status StrCopy(SString T, SString S);
/*
* 清理字符串S中的空白包括清理不可打印字符和清理空格。
*
*【注】
* 该函数是在本章中新增的。
*/
Status ClearBlank(SString S);
#endif

View File

@@ -57,3 +57,7 @@ add_subdirectory(0708_ArticulationPoints)
add_subdirectory(0709_TopologicalSorting)
add_subdirectory(0710_CriticalPathMethod)
add_subdirectory(0711_ShortestPaths)
add_subdirectory(0801_BoundaryTagMethod)
add_subdirectory(0802_BuddySystem)
add_subdirectory(0803_GarbageCollection)

View File

@@ -0,0 +1,48 @@
#include <stdio.h>
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
int main(int argc, char* argv[]) {
Space pav;
Space p[12]; // 记录申请到的内存的指针
int s[12] = {10, 20, 30, 50, 5, 15, 10, 5, 15, 15, 2, 20}; // 申请的空间大小
int i = 0;
printf("████████ InitSpace \n");
{
int max = 200; // 初值建议为20的倍数目的是打印出来可以对齐
printf("初始化包含 %d 个\"\"的内存块后,当前内存布局为:\n", max);
pav = InitSpace(max);
PrintMemoryLayout();
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ AllocBoundTag \n");
{
for(i = 0; i < 12; i++) {
printf("████ %2d> 申请 %d 个\"\"的内存后,当前内存布局为:\n", i + 1, s[i]);
p[i] = AllocBoundTag(&pav, s[i]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
printf("████████ FreeBoundTag \n");
{
// 定义一个指针回收顺序
int a[10] = {7, 3, 10, 6, 8, 5, 11, 1, 0, 4};
for(i = 0; i < 10; i++) {
printf("回收 p%d 指向的内存...\n", a[i] + 1);
FreeBoundTag(&pav, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}

View File

@@ -0,0 +1,331 @@
/*==============
* 边界标识法
*
* 包含算法: 8.1
===============*/
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
/*
* 全局变量:永远指向初始空间的头部。
* 该变量有两个作用:
* 1.用来追踪内存布局,即查看内存的使用情况。
* 2.指示内存的起始和结尾
*/
static Space av;
// 记录空间容量,用于追踪内存布局
static int len;
/*
* 初始化一块总大小为n个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head和foot。
*/
Space InitSpace(int n) {
Space space, head, foot;
// 初始化空闲内存
space = (Space) malloc(n * sizeof(WORD));
if(space == NULL) {
return NULL;
}
// 初始化头部信息
head = space;
head->llink = space; // 前驱指向自身
head->rlink = space; // 后继指向自身
head->size = n; // 空间大小已经包含了head和foot
head->tag = 0; // 标记空间空闲
// 初始化底部信息
foot = FootLoc(head); // 初始化底部指针
foot->uplink = head; // 底部域链接到本结点的头部
foot->tag = 0; // 标记空间空闲
// 记下空间的起始位置和容量
av = space;
len = n;
return space;
}
/*
* ████████ 算法8.1 ████████
*
* 边界标识法的内存分配算法
*
* 从空间pav中申请一块大小至少为n的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
* 为了使分配后的剩余块尽量均匀分布每次分配完之后都要把空间指针pav向前移动具体描述参见教材。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.为了避免空间碎片化过快这里增加了一个容差e具体含义参考教材描述。
* 3.这里申请分配n个字的空间指的是已经完成换算的空间。
* 比如用户想要容量为10个字的空间但每个空间又包含head和foot这两个字存储空间使用信息
* 因此实际上分配的空间大小应为12个字这个"12"就是n的含义。
* 教材中提到"head和foot在分配时忽略不计",这样是为了伪码书写的方便。实际写代码时,这两个空间是不能忽略的。
* 但是如果按照上面的描述把n解释为已经换算后空间而不是用户原始申请的空间
* 那么既满足了没有忽略空间,又满足了在书面效果上,跟教材伪码统一,可谓两全其美。
*/
Space AllocBoundTag(Space* pav, int n) {
Space p, f;
/*
* 增加一个判断如果换算后的空间容量小于3则直接返回。
* 因为head和foot本身就占了2个字空间用户至少申请1个字的话总申请空间至少为3个字。
*/
if(n < 3) {
printf("日志:分配失败!申请的\"\"数应当不小于3\n");
return NULL;
}
// 查找不小于n的空闲块
for(p = *pav; p && p->size < n && p->rlink != *pav; p = p->rlink) {
}
// 找不到合适的空闲块,返回空指针
if(!p || p->size < n) {
return NULL;
}
/* 至此p指向了满足条件的空闲块 */
// 让f指向该空闲块的底部
f = FootLoc(p);
// pav指向p结点的后继结点即指向下一个空闲块
*pav = p->rlink;
// 如果空闲块比申请的容量大不了多少,则需要整块分配,即不保留<=e的剩余量
if(p->size - n <= e) {
// 由于上面已经让pav指向了下一个空闲块所以如果pav与p相等说明此时只有一个空闲块了(注意空闲块链表是双循环的)
if(*pav == p) {
*pav = NULL; // 如果仅剩一个空闲块了,那么被占用后,可利用空间表变为空表
// 否则,在表中删除分配的结点
} else {
(*pav)->llink = p->llink; // 修改pav的前驱
p->llink->rlink = *pav; // 修改pav前驱的后继
/* 在上述操作中p结点的前驱与后继并没有改变这是为了方便将来的回收操作 */
}
// 更新占用块为占用状态
p->tag = f->tag = 1;
printf("日志:分配成功!申请 %d 个\"\",实际分配了 %d 个\"\"。空闲空间容量为 %d ...\n", n, n + e, AvailableSpace(*pav));
// 如果空闲块很大则从中间切割占用后面的n个字
} else {
f->tag = 1; // 修改分配块的底部标志,表示其处于占用状态
p->size -= n; // 设置剩余块大小
f = FootLoc(p); // 计算剩余块的新底部位置
f->tag = 0; // 设置新底部标志为空闲
f->uplink = p; // 新底部依然指向空闲块头部
p = f + 1; // 计算分配块的新头部位置
p->tag = 1; // 设置分配块新头部标志为占用
p->size = n; // 设置分配块的容量
// 修改分配块底部的链接(教材中缺失了此步骤)
(FootLoc(p))->uplink = p;
printf("日志:分配成功!申请并分配了 %d 个\"\"。空闲空间容量为 %d ...\n", n, AvailableSpace(*pav));
}
// 返回分配块首地址
return p;
}
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注:以下描述中的"释放块"就是指针p指向的内存
*/
void FreeBoundTag(Space* pav, Space p) {
Space h, f, q;
int Ltag, Rtag;
if(p == NULL) {
printf("日志:回收失败!内存指针为空。空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
Ltag = p == av ? 1 : (p - 1)->tag; // 块p的左邻区标志。特别注意如果块p位于内存起始处则认为它的左邻区为占用。
Rtag = (p + p->size) == (av + len) ? 1 : (p + p->size)->tag; // 块p的右邻区标志。特别注意如果块p位于内存结尾处则认为它的右邻区为占用。
/*
* 1.释放块的左、右邻区均为占用块
*
* 此时仅需要将块p插入到pav所指结点的之前即可(当然,插入到之后也是可以的)
*/
if(Ltag == 1 && Rtag == 1) {
printf("日志:\"释放块\"的容量为 %d ,且它的左、右邻区均为占用块...\n", p->size);
f = FootLoc(p);
f->uplink = p;
f->tag = 0;
p->tag = 0;
// 空闲链表为空时直接将块p变为新的独立的空闲块
if((*pav) == NULL) {
*pav = p->llink = p->rlink = p;
// 否则将块p插入到pav之前
} else {
q = (*pav)->llink;
p->rlink = *pav;
p->llink = q;
q->rlink = (*pav)->llink = p;
// 令刚释放的结点成为下次分配空间时最先查找的结点
*pav = p;
}
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 2.释放块的左邻区为空闲块,右邻区为占用块
*
* 此时需要合并左邻区与释放块
*/
if(Ltag == 0 && Rtag == 1) {
printf("日志:\"释放块\"的容量为 %d ,且它的左邻区为空闲块,右邻区为占用块...\n", p->size);
h = (p - 1)->uplink; // 左邻区的头部,这将成为合并后的新块的头部
h->size += p->size; // 左邻区容量增大
f = FootLoc(p); // 将释放块的底部做为合并后的新块的底部
f->uplink = h;
f->tag = 0;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 3.释放块的左邻区为占用块,右邻区为空闲块
*
* 此时需要合并释放块与右邻区
*/
if(Ltag == 1 && Rtag == 0) {
printf("日志:\"释放块\"的容量为 %d ,且它的左邻区为占用块,右邻区为空闲块...\n", p->size);
h = p + p->size; // 右邻区的头部
f = FootLoc(h); // 右邻区的底部,这将成为合并后的新块的底部
f->uplink = p; // 释放块的头部将作为合并后新块的头部
p->tag = 0;
p->size += h->size;
// 释放块的头部链接域要更新为与右邻区头部的链接域一致
p->llink = h->llink;
p->rlink = h->rlink;
h->llink->rlink = p;
h->rlink->llink = p;
// pav指向合并后的结点的新头部
*pav = p;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 4.释放块的左、右邻区均为空闲块
*
* 此时需要合并左邻区、释放块、右邻区
*/
if(Ltag == 0 && Rtag == 0) {
printf("日志:\"释放块\"的容量为 %d ,且它的左、右邻区均为空闲块...\n", p->size);
h = (p - 1)->uplink; // 左邻区的头部,这将成为合并后的新块的头部
q = p + p->size; // 右邻区的头部
f = FootLoc(q); // 右邻区的底部,这将成为合并后的新块的底部
h->size += p->size + q->size; // 合并后的新块大小
f->uplink = h; // 新块底部信息也要更新
// 移除右邻区
q->rlink->llink = q->llink;
q->llink->rlink = q->rlink;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
}
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout() {
Space p;
int count;
int i;
p = av;
count = av->size;
for(i = 1; i <= count; i++) {
if(p->tag == 0) {
printf("");
} else {
printf("");
}
if(i == count && count < len) {
p = p + p->size;
count += p->size;
printf("|");
} else {
printf(" ");
}
// 每隔20个换一下行
if(i % 20 == 0) {
printf("\n");
}
}
if(len % 20 != 0) {
printf("\n");
}
}
/*
* 计算可用的空闲空间容量
*
* 注:仅限内部使用,用在日志打印中
*/
static int AvailableSpace(Space pav) {
Space p;
int count;
if(pav == NULL) {
return 0;
}
p = pav;
count = 0;
do {
count += p->size;
p = p->rlink;
} while(p != pav);
return count;
}

View File

@@ -0,0 +1,82 @@
[Project]
FileName=BoundaryTagMethod.dev
Name=BoundaryTagMethod
Type=1
Ver=2
ObjFiles=
Includes=
Libs=
PrivateResource=
ResourceIncludes=
MakeIncludes=
Compiler=
CppCompiler=
Linker=
IsCpp=0
Icon=
ExeOutput=
ObjectOutput=
LogOutput=
LogOutputEnabled=0
OverrideOutput=0
OverrideOutputName=
HostApplication=
UseCustomMakefile=0
CustomMakefile=
CommandLine=
Folders=
IncludeVersionInfo=0
SupportXPThemes=0
CompilerSet=1
CompilerSettings=0000000000000000001000000
UnitCount=3
[VersionInfo]
Major=1
Minor=0
Release=0
Build=0
LanguageID=1033
CharsetID=1252
CompanyName=
FileVersion=
FileDescription=Developed using the Dev-C++ IDE
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=
AutoIncBuildNr=0
SyncProduct=1
[Unit2]
FileName=BoundaryTagMethod.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=BoundaryTagMethod-main.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit1]
FileName=BoundaryTagMethod.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

View File

@@ -0,0 +1,97 @@
/*==============
* 边界标识法
*
* 包含算法: 8.1
===============*/
#ifndef BOUNDARYTAGMETHOD_H
#define BOUNDARYTAGMETHOD_H
#include <stdio.h>
#include <stdlib.h>
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define e 5 // 分配空间时用到的容差
#define FootLoc(p) p+(p->size)-1 // 将p指针定位到p所指内存块的底部
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
/*
* 注:
* 教材中将llink和uplink封装在了一个联合体中个人感觉这个封装有些鸡肋。
* 一方面head和foot的公共部分其实只有tag如果真想用联合体那么应当把size和rlink也封装起来。
* 另一方面,这个封装节省的空间很有限,而且直接影响了代码的可读性。
* 这里只是教学代码,而不是真实的系统代码,所以空间考虑在其次,原理展示为首要任务。
* 此外,教材中的伪码中也并没有考虑这个联合体,而是直接进行操作的。
* 综上所述,这里去除了教材中的联合体结构。
* 如想观察带有联合体的写法可以参考CFree分支的代码。
*/
int tag; // 块标志0空闲1占用头部和尾部均有
struct WORD* llink; // 头部域,指向前驱结点
struct WORD* rlink; // 头部域,指向后继结点
int size; // 头部域,块大小
struct WORD* uplink; // 底部域,指向本结点头部
} WORD;
typedef WORD* Space; // Space指向可利用空间的指针类型
/*
* 初始化一块大小为n个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head和foot。
*/
Space InitSpace(int n);
/*
* ████████ 算法8.1 ████████
*
* 边界标识法的内存分配算法
*
* 从空间pav中申请一块大小至少为n的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
* 为了使分配后的剩余块尽量均匀分布每次分配完之后都要把空间指针pav向前移动具体描述参见教材。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.为了避免空间碎片化过快这里增加了一个容差e具体含义参考教材描述。
* 3.这里申请分配n个字的空间指的是已经完成换算的空间。
* 比如用户想要容量为10个字的空间但每个空间又包含head和foot这两个字存储空间使用信息
* 因此实际上分配的空间大小应为12个字这个"12"就是n的含义。
* 教材中提到"head和foot在分配时忽略不计",这样是为了伪码书写的方便。实际写代码时,这两个空间是不能忽略的。
* 但是如果按照上面的描述把n解释为已经换算后空间而不是用户原始申请的空间
* 那么既满足了没有忽略空间,又满足了在书面效果上,跟教材伪码统一,可谓两全其美。
*/
Space AllocBoundTag(Space* pav, int n);
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void FreeBoundTag(Space* pav, Space p);
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout();
/*
* 计算可用的空闲空间容量
*
* 注:仅限内部使用,用在日志打印中
*/
static int AvailableSpace(Space pav);
#endif

View File

@@ -0,0 +1,45 @@
#include <stdio.h>
#include "BuddySystem.h" //**▲08 动态存储管理**//
int main(int argc, char* argv[]) {
FreeList avail;
WORD* p[8]; // 记录申请到的内存的指针
int s[8] = {4, 5, 6, 7, 1, 5, 3, 9}; // 申请的空间大小
int i;
printf("████████ InitSpace \n");
{
printf("初始化一个内存块...\n");
InitSpace(avail);
PrintMemoryLayout();
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ AllocBuddy \n");
{
for(i = 0; i < 8; i++) {
printf("申请大小为 %d 个字的内存块...\n", s[i]);
p[i] = AllocBuddy(avail, s[i]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
printf("████████ FreeBuddy \n");
{
// 定义一个指针回收顺序
int a[8] = {2, 0, 5, 7, 1, 4, 3, 6};
for(i = 0; i < 8; i++) {
FreeBuddy(avail, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}

View File

@@ -0,0 +1,284 @@
/*==============
* 伙伴系统
*
* 包含算法: 8.2
===============*/
#include "BuddySystem.h"
// 记录内存的起始地址,在计算伙伴块时候需要用到
WORD* start;
/*
* 初始化一块大小为2^M个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head。
*/
void InitSpace(FreeList avail) {
int k;
WORD* r;
// 遍历M+1个元素
for(k = 0; k <= M; k++) {
avail[k].nodesize = (int) pow(2, k);
avail[k].first = NULL;
}
r = (WORD*) malloc((int) pow(2, M) * sizeof(WORD));
if(r == NULL) {
exit(OVERFLOW);
}
// 设置头部信息
r->llink = r->rlink = r;
r->tag = 0;
r->kval = M;
avail[M].first = r;
start = r;
}
/*
* ████████ 算法8.2 ████████
*
* 伙伴系统的内存分配算法
*
* 从空间avail中申请一块大小至少为n(原始值)的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.这里申请分配n个字的空间指的是用户申请的原始空间。
* 实际在申请时还要考虑到每个块前面有1个字的head信息即经过换算后实际需要申请(n+1)个字。
* 这里的n与算法8.1里面的n含义正好相反需要注意。
*/
WORD* AllocBuddy(FreeList avail, int n) {
int k, i;
WORD* pa, * pre, * suc, * pi;
/*
* 增加一个判断如果换算后的空间容量小于1则直接返回。
*/
if(n < 1) {
printf("日志:分配失败!申请的\"\"数应当不小于1\n");
return NULL;
}
// 查找不小于n的空闲块
for(k = 0; k <= M && (avail[k].nodesize < n + 1 || !avail[k].first); k++) {
}
// 找不到合适的空闲块,返回空指针
if(k > M) {
printf("日志:分配失败!没有足够的空闲块\n");
return NULL;
}
pa = avail[k].first; // 指向可分配子表的第一个结点
pre = pa->llink; // 分别记下前驱和后继
suc = pa->rlink;
// 如果此处仅有一个空闲块,则分配后该子表变为空
if(pa == suc) {
avail[k].first = NULL;
// 否则,从链表头部摘下一个可用的空闲块,并将子表头指针指向下一个空闲块
} else {
pre->rlink = suc;
suc->llink = pre;
avail[k].first = suc;
}
/*
* 从k-1处开始逆向遍历FreeList数组向其中填充剩余的空闲块。
* 剩余的空闲块是对剩余可用空间的拆分。
*
* 这里用到一个公式2^m-2^n = 2^n+2^(n+1)+...+2^(m-1)
* 比如初始容量为2^16此时总共申请1500个字那么需要分配一块2^11的空闲块给它。
* 分配完之后剩余的容量为2^16-2^11 = 2^11+2^12+2^13+2^14+2^15。
* 这些剩余容量可以拆分为5个空闲块分别存储到15、14、13、12、11这五个索引处。
*/
for(i = 1; k - i >= 0 && avail[k - i].nodesize >= n + 1; i++) {
pi = pa + (int) pow(2, k - i); // 每次将pi指向剩余空间的后一半
pi->rlink = pi->llink = pi; // 初始化pi的前驱和后继
pi->tag = 0; // 标记为空闲块
pi->kval = k - i; // 设置该块的容量标志真实容量为2^(k-i)
avail[k - i].first = pi;
/*
* 注:
* 上面分解出来的pi直接添加到了avail中并没有考虑同位置处会不会有别的容量相同的空闲块。
* 这里不需要考虑的原因是如果同位置处已经存在别的容量相同的空闲块,
* 那么这里根本不需要分解剩余空间,换句话说,连这个循环都进不来。
* 只要进来这个循环,说明目标位置处已经为空了,没有找到合适的空闲块,所以这才进一步向高游标处寻找空闲块。
*/
}
// 最后剩下的最靠前的空间就是需要分配的空间(这里没有设置pa的前驱和后继因为没必要)
pa->tag = 1;
pa->kval = k - (--i);
printf("日志:分配成功!用户申请 %d 个字,系统申请 %d 个字,实际分配 %d 个字\n", n, n + 1, (int) pow(2, pa->kval));
return pa;
}
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void FreeBuddy(FreeList avail, WORD* p) {
int k;
WORD* r;
WORD* buddy = Buddy(p);
if(p == NULL) {
return;
}
/*
* 这里将p结点简单地插入到avail中包含三种情形
* 1.伙伴块非空闲
* 2.伙伴空闲但是伙伴的大小跟p的大小不一致说明伙伴还没拼合好
* 3.p拼接成了最后一个最大的空闲块
*/
if(buddy->tag == 1 || buddy->kval != p->kval || p->kval == M) {
for(k = 0; k <= M && k < p->kval; k++) {
// 查找p结点应当进入的插槽
}
// 找到插槽,采用头插法将空闲块插入到目标插槽
if(k <= M && k == p->kval) {
p->tag = 0;
if(avail[k].first == NULL) {
p->llink = p->rlink = p;
} else {
p->llink = avail[k].first->llink;
p->rlink = avail[k].first;
p->llink->rlink = p;
p->rlink->llink = p;
}
avail[k].first = p;
printf("日志:回收成功![%d, (2^%d)]进入插槽 %d 的空闲块链表上\n", (int) (p - start), k, k);
}
// 如果伙伴块是空闲的,此时应当进行合并操作
} else {
for(k = 0; k <= M && k < p->kval; k++) {
// 查找伙伴块所在的插槽
}
// 找到插槽,将伙伴块从空闲块链表中摘下来
if(k <= M && k == p->kval) {
// 伙伴在链表第一个位置
if(avail[k].first == buddy) {
buddy->rlink->llink = buddy->llink;
buddy->llink->rlink = buddy->rlink;
avail[k].first = buddy->rlink;
// 伙伴在中间位置
} else {
for(r = avail[k].first; r->rlink != buddy; r = r->rlink) {
// 查找伙伴r指向伙伴的前驱
}
r->rlink = buddy->rlink;
buddy->rlink->llink = r;
}
printf("日志:合并成功![%d, (2^%d)]和[%d, (2^%d)]合并成了", (int) (p - start), k, (int) (buddy - start), k);
// 合并之前,需要确定哪个伙伴靠前
if(p < buddy) {
p->tag = 0;
} else {
p = buddy;
}
p->kval = k + 1; // 指数增一后,即完成合并
printf("[%d, (2^%d)]\n", (int) (p - start), k + 1);
// 出现新的空闲块之后,要进入递归,查看该空闲块是否也存在空闲伙伴
FreeBuddy(avail, p);
}
}
}
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout() {
int i, count, total;
WORD* p;
printf("|");
p = start;
count = (int) pow(2, p->kval);
for(i = 1; i <= count; i++) {
if(p->tag == 0) {
printf("_");
} else {
printf("*");
}
// 进入到下一个块
if(i == count && count < (int) pow(2, M)) {
p = start + count;
count += (int) pow(2, p->kval);
printf("|");
}
}
printf("|\n");
}
/*
* 查找块p的伙伴
*
* 将一个空闲块对半分裂后,会生成的两个小空闲块,这两个小空闲块互为伙伴。
*
* 计算伙伴的算法为:
* 对于起始地址为p大小为2^k的内存块
* 1.若 p MOD 2^(k+1) == 0 ,则p的伙伴块的起始地址为p+2^k
* 2.若 p MOD 2^(k+1) == 2^k ,则p的伙伴块的起始地址为p-2^k。
*
* 注:仅限内部使用,用在回收算法中
*/
static WORD* Buddy(WORD* p) {
long s, m, n;
if(p == NULL) {
return NULL;
}
// start是整个空闲块的绝对起始地址s是p在伙伴系统中的绝对地址从0开始
s = p - start;
if(s < 0) {
return NULL;
}
m = (long) pow(2, p->kval);
n = (long) pow(2, p->kval + 1);
if(s % n == 0) {
return p + m;
}
if(s % n == m) {
return p - m;
}
return NULL;
}

View File

@@ -0,0 +1,82 @@
[Project]
FileName=BuddySystem.dev
Name=BuddySystem
Type=1
Ver=2
ObjFiles=
Includes=
Libs=
PrivateResource=
ResourceIncludes=
MakeIncludes=
Compiler=
CppCompiler=
Linker=
IsCpp=0
Icon=
ExeOutput=
ObjectOutput=
LogOutput=
LogOutputEnabled=0
OverrideOutput=0
OverrideOutputName=
HostApplication=
UseCustomMakefile=0
CustomMakefile=
CommandLine=
Folders=
IncludeVersionInfo=0
SupportXPThemes=0
CompilerSet=1
CompilerSettings=0000000000000000001000000
UnitCount=3
[VersionInfo]
Major=1
Minor=0
Release=0
Build=0
LanguageID=1033
CharsetID=1252
CompanyName=
FileVersion=
FileDescription=Developed using the Dev-C++ IDE
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=
AutoIncBuildNr=0
SyncProduct=1
[Unit1]
FileName=BuddySystem.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=BuddySystem.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=BuddySystem-main.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

View File

@@ -0,0 +1,90 @@
/*==============
* 伙伴系统
*
* 包含算法: 8.2
===============*/
#ifndef BUDDYSYSTEM_H
#define BUDDYSYSTEM_H
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define M 6 // 假设总空间大小为2^M个字子表个数为M+1
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
struct WORD* llink; // 头部域的前驱指针
struct WORD* rlink; // 头部域的后继指针
int tag; // 头部域的块标志0:空闲1:占用
int kval; // 指示块的大小比如其值为K时表示该块的大小为2^K
} WORD;
// 表头向量类型
typedef struct HeadNode {
int nodesize; // 该链表的空闲块的大小
WORD* first; // 该链表的表头指针
} FreeList[M + 1];
/*
* 初始化一块大小为2^M个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head。
*/
void InitSpace(FreeList avail);
/*
* ████████ 算法8.2 ████████
*
* 伙伴系统的内存分配算法
*
* 从空间avail中申请一块大小至少为n(原始值)的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.这里申请分配n个字的空间指的是用户申请的原始空间。
* 实际在申请时还要考虑到每个块前面有1个字的head信息即经过换算后实际需要申请(n+1)个字。
* 这里的n与算法8.1里面的n含义正好相反需要注意。
*/
WORD* AllocBuddy(FreeList avail, int n);
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void FreeBuddy(FreeList avail, WORD* p);
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout();
/*
* 查找块p的伙伴
*
* 将一个空闲块对半分裂后,会生成的两个小空闲块,这两个小空闲块互为伙伴。
*
* 计算伙伴的算法为:
* 对于起始地址为p大小为2^k的内存块
* 1.若 p MOD 2^(k+1) == 0 ,则p的伙伴块的起始地址为p+2^k
* 2.若 p MOD 2^(k+1) == 2^k ,则p的伙伴块的起始地址为p-2^k。
*
* 注:仅限内部使用,用在回收算法中
*/
static WORD* Buddy(WORD* p);
#endif

View File

@@ -0,0 +1,202 @@
/*============================
* 广义表的头尾链表存储表示
*
* 包含算法: 5.5、5.6、5.7、5.8
=============================*/
#include "GList-HT.h" //**▲05 数组和广义表**//
/*
* 初始化
*
* 初始化空的广义表长度为0深度为1。
*
*【注】
* 需要对每一层去掉括号考察
*/
Status InitGList(GList* L) {
if(L == NULL) {
return ERROR;
}
*L = NULL;
return OK;
}
/*
* ████████ 算法5.7 ████████
*
* 创建
*
* 由字符串S创建广义表L。
*/
Status CreateGList(GList* L, SString S) {
SString emp; // 代表空广义表的字符串
SString hsub, sub;
GList p, q;
if(L == NULL) {
return ERROR;
}
// 清理字符串S中的空白包括清理不可打印字符和清理空格
ClearBlank(S);
if(StrEmpty(S)) {
return ERROR;
}
StrAssign(emp, "()");
/*
* 如果输入串为(),则代表需要创建空的广义表
*
*【注】
* 教材这里的代码是有问题的。
* StrCompare的返回值指示的是两个字符串的大小而不是指示两个字符串是否相等。
* 如果给定的S与()相等返回值应当是0。
*/
if(!StrCompare(S, emp)) {
*L = NULL;
} else {
*L = (GList) malloc(sizeof(GLNode));
if(*L == NULL) {
exit(OVERFLOW);
}
// 初始化标记为0意思是该结点未访问
(*L)->mark = 0;
// 创建原子
if(StrLength(S) == 1) {
(*L)->tag = Atom;
(*L)->Node.atom = S[1];
} else {
(*L)->tag = List;
p = *L;
// 去掉最外层括号
SubString(sub, S, 2, StrLength(S) - 2);
// 重复建n个子表
do {
// 从sub中分离出表头串hsub分离完成后sub也会发生变化
sever(hsub, sub);
// 递归创建广义表
CreateGList(&(p->Node.ptr.hp), hsub);
q = p;
// 如果表尾不为空,需要继续处理表尾
if(!StrEmpty(sub)) {
p = (GList) malloc(sizeof(GLNode));
if(p == NULL) {
exit(OVERFLOW);
}
// 初始化标记为0意思是该结点未访问
p->mark = 0;
p->tag = List;
q->Node.ptr.tp = p;
}
} while(!StrEmpty(sub));
q->Node.ptr.tp = NULL;
}
}
return OK;
}
/*
* 图形化输出
*
* 带括号输出广义表L。
*/
void PrintGList(GList L) {
Print(L, Head);
printf("\n");
}
/*
* 图形化输出的内部实现mark是图形化输出标记。
*/
static void Print(GList L, Mark mark) {
// L为空
if(L == NULL) {
if(mark == Head) {
printf("(");
}
printf(")");
// L不为空时
} else {
// 对于原子结点,输出原子
if(L->tag == Atom) {
printf("%c", L->Node.atom);
// 对于子表结点,要对表头、表尾分别讨论
} else {
if(mark == Head) {
printf("(");
} else {
printf(",");
}
Print(L->Node.ptr.hp, Head);
Print(L->Node.ptr.tp, Tail);
}
}
}
/*
* ████████ 算法5.8 ████████
*
* 将非空串str分割成两部分hsub为第一个','之前的子串str为第一个','之后的子串。
*
*【注】
* 1.这里假设字符串str输入正确其中无空白符号且str【已经脱去最外层括号】。
* 2.分离完成后str也会发生变化
*/
static void sever(SString hstr, SString str) {
int i, k, n;
SString ch;
n = StrLength(str);
i = 0; // 遍历字符串时的游标
k = 0; // 标记遇到的未配对括号数量
do {
++i;
// 截取str第一个字符
SubString(ch, str, i, 1);
if(ch[1] == '(') {
++k;
}
if(ch[1] == ')') {
--k;
}
} while(i < n && (ch[1] != ',' || k != 0));
// 如果存在多个广义表结点
if(i < n) {
SubString(hstr, str, 1, i - 1);
SubString(str, str, i + 1, n - i);
// 只有一个广义表结点
} else {
StrCopy(hstr, str);
ClearString(str);
}
}

View File

@@ -0,0 +1,97 @@
/*============================
* 广义表的头尾链表存储表示
*
* 包含算法: 5.5、5.6、5.7、5.8
=============================*/
#ifndef GLIST_HT_H
#define GLIST_HT_H
#include <stdio.h>
#include <stdlib.h> // 提供 malloc、realloc、free、exit 原型
#include "Status.h" //**▲01 绪论**//
#include "SString.h" //**▲04 串**//
/* 原子元素类型 */
typedef char AtomType;
/*
* 广义表结点标记
*
* Atom-0原子结点
* List-1表结点
*/
typedef enum { Atom, List } ElemTag;
/* 广义表(头尾链表存储表示)类型定义 */
typedef struct GLNode {
int mark; // 为第8章第5节新增的变量用来给广义表打上遍历标记会在创建中初始化为0
ElemTag tag; // 公共标记,用于区分原子结点和表结点
// 原子结点和表结点的联合部分
union {
AtomType atom; // atom是原子结点的值域AtomType由用户定义
struct {
struct GLNode* hp; // 指向表头
struct GLNode* tp; // 指向表尾
} ptr; // 表结点的指针域
} Node;
} GLNode;
/* 广义表类型 */
typedef GLNode* GList;
/*
* 图形化输出标记
*
* Head代表广义表指针来自表头
* Tail代表广义表指针来自表尾
*/
typedef enum { Head, Tail } Mark;
/*
* 初始化
*
* 初始化空的广义表长度为0深度为1。
*
*【注】
* 需要对每一层去掉括号考察
*/
Status InitGList(GList* L);
/*
* ████████ 算法5.7 ████████
*
* 创建
*
* 由字符串S创建广义表L。
*/
Status CreateGList(GList* L, SString S);
/*
* 图形化输出
*
* 带括号输出广义表L。
*/
void PrintGList(GList L);
/*
* 图形化输出的内部实现mark是图形化输出标记。
*/
static void Print(GList L, Mark mark);
/*
* ████████ 算法5.8 ████████
*
* 将非空串str分割成两部分hsub为第一个','之前的子串str为第一个','之后的子串。
*
*【注】
* 1.这里假设字符串str输入正确其中无空白符号且str【已经脱去最外层括号】。
* 2.分离完成后str也会发生变化
*/
static void sever(SString hstr, SString str);
#endif

View File

@@ -0,0 +1,68 @@
#include <stdio.h>
#include "GarbageCollection.h" //**▲08 动态存储管理**//
// 遍历广义表
void Traverse(GList L, void(Visit)(GList));
// 打印广义表结点信息
void PrintInfo(GList L);
int main(int argc, char* argv[]) {
GList G;
printf("创建并输出广义表:");
{
SString S;
char* s = "((),(e),(a,(b,c,d)))";
InitGList(&G);
StrAssign(S, s);
CreateGList(&G, S);
PrintGList(G);
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ MakeList \n");
{
printf("访问前的标志状态...\n");
Traverse(G, PrintInfo);
printf("\n");
MakeList(G);
printf("访问后的标志状态...\n");
Traverse(G, PrintInfo);
printf("\n");
}
PressEnterToContinue(debug);
return 0;
}
// 遍历广义表
void Traverse(GList L, void(Visit)(GList)) {
if(L == NULL) {
return;
}
Visit(L);
if(L->tag == List) {
Traverse(L->Node.ptr.hp, Visit);
Traverse(L->Node.ptr.tp, Visit);
}
}
// 打印广义表结点信息
void PrintInfo(GList L) {
if(L->tag == Atom) {
printf("mark = %d 原子结点:%c\n", L->mark, L->Node.atom);
} else {
printf("mark = %d 表结点\n", L->mark);
}
}

View File

@@ -0,0 +1,79 @@
/*==============
* 无用单元收集
*
* 包含算法: 8.3
===============*/
#include "GarbageCollection.h"
/*
* ████████ 算法8.3 ████████
*
* 遍历广义表(不使用栈)。
*/
void MakeList(GList G) {
GList t, p, q;
Status finished;
if(!G || G->mark != 0) {
return;
}
t = NULL; // t为p的母表
p = G;
finished = FALSE;
while(!finished) {
while(p->mark == 0) {
p->mark = 1; // MakeHead(p)的细化
q = p->Node.ptr.hp; // q指向p的表头
if(q != NULL && q->mark == 0) {
// 表头为原子结点
if(q->tag == Atom) {
q->mark = 1;
// 继续遍历子表
} else {
p->Node.ptr.hp = t;
p->tag = Atom;
t = p;
p = q;
}
}
} // 完成对表头的标记
q = p->Node.ptr.tp;
// 继续遍历表尾
if(q != NULL && q->mark == 0) {
p->Node.ptr.tp = t;
t = p;
p = q;
// BackTrack(finished)的细化
} else {
// 表结点,从表尾回溯
while(t && t->tag == List) {
q = t;
t = q->Node.ptr.tp;
q->Node.ptr.tp = p;
p = q;
}
// 结束
if(t == NULL) {
finished = TRUE;
// 从表头回溯
} else {
q = t;
t = q->Node.ptr.hp;
q->Node.ptr.hp = p;
p = q;
p->tag = List;
}// 继续遍历表尾
}
}
}

View File

@@ -0,0 +1,122 @@
[Project]
FileName=GarbageCollection.dev
Name=GarbageCollection
Type=1
Ver=2
ObjFiles=
Includes=
Libs=
PrivateResource=
ResourceIncludes=
MakeIncludes=
Compiler=
CppCompiler=
Linker=
IsCpp=0
Icon=
ExeOutput=
ObjectOutput=
LogOutput=
LogOutputEnabled=0
OverrideOutput=0
OverrideOutputName=
HostApplication=
UseCustomMakefile=0
CustomMakefile=
CommandLine=
Folders=
IncludeVersionInfo=0
SupportXPThemes=0
CompilerSet=1
CompilerSettings=0000000000000000001000000
UnitCount=7
[VersionInfo]
Major=1
Minor=0
Release=0
Build=0
LanguageID=1033
CharsetID=1252
CompanyName=
FileVersion=
FileDescription=Developed using the Dev-C++ IDE
InternalName=
LegalCopyright=
LegalTrademarks=
OriginalFilename=
ProductName=
ProductVersion=
AutoIncBuildNr=0
SyncProduct=1
[Unit1]
FileName=GarbageCollection.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=GarbageCollection.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=GarbageCollection-main.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit4]
FileName=GList-HT.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit5]
FileName=GList-HT.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit6]
FileName=SString.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit7]
FileName=SString.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

View File

@@ -0,0 +1,22 @@
/*==============
* 无用单元收集
*
* 包含算法: 8.3
===============*/
#ifndef GARBAGECOLLECTION_H
#define GARBAGECOLLECTION_H
#include <stdio.h>
#include "Status.h" //**▲01 绪论**//
#include "GList-HT.h" //**▲05 数组和广义表**//
/*
* ████████ 算法8.3 ████████
*
* 遍历广义表(不使用栈)。
*/
void MakeList(GList G);
#endif

View File

@@ -0,0 +1,168 @@
/*=============================
* 串的定长顺序存储表示(顺序串)
*
* 包含算法: 4.1、4.2、4.3、4.5
==============================*/
#include "SString.h" //**▲04 串**//
/*
* 初始化
*
* 构造一个值为chars的串T。
*
*【注】
* 该操作属于最小操作子集
*/
Status StrAssign(SString T, const char* chars) {
int i, len;
len = (int) strlen(chars);
// chars过长
if(len > MAXSTRLEN) {
return ERROR;
}
T[0] = len;
for(i = 1; i <= len; i++) {
T[i] = chars[i - 1];
}
return OK;
}
/*
* 清空
*
* 将串S清空。
*/
Status ClearString(SString S) {
// 只需要将长度置为0就可以
S[0] = 0;
return OK;
}
/*
* 判空
*
* 判断串S中是否包含有效数据。
*
* 返回值:
* TRUE : 串S为空
* FALSE: 串S不为空
*/
Status StrEmpty(SString S) {
return S[0] == 0 ? TRUE : FALSE;
}
/*
* 计数
*
* 返回串S中元素的个数。
*
*【注】
* 该操作属于最小操作子集
*/
int StrLength(SString S) {
return S[0];
}
/*
* ████████ 算法4.3 ████████
*
* 求子串
*
* 用Sub返回S[pos, pos+len-1]。
* 返回值指示是否截取成功。
*
*【注】
* 该操作属于最小操作子集
*/
Status SubString(SString Sub, SString S, int pos, int len) {
int i;
if(pos < 1 || pos > S[0] || len < 0 || pos + len - 1 > S[0]) {
return ERROR;
}
// 复制元素
for(i = 1; i <= len; i++) {
Sub[i] = S[pos + i - 1];
}
// 确定新长度
Sub[0] = len;
return OK;
}
/*
* 比较
*
* 比较串S和串T返回比较结果。
*
*【注】
* 该操作属于最小操作子集
*/
int StrCompare(SString S, SString T) {
int i = 1;
while(i <= S[0] && i <= T[0]) {
// 遇到不同的字符时,比较其大小
if(S[i] != T[i]) {
return S[i] - T[i];
}
i++;
}
return S[0] - T[0];
}
/*
* 复制
*
* 将串S复制到串T。
*/
Status StrCopy(SString T, SString S) {
int i;
// 连同长度信息一起复制
for(i = 0; i <= S[0]; i++) {
T[i] = S[i];
}
return OK;
}
/*
* 清理字符串S中的空白包括清理不可打印字符和清理空格。
*
*【注】
* 该函数是在本章中新增的。
*/
Status ClearBlank(SString S) {
int i, j;
// 如果是空串
if(S[0] == 0) {
return ERROR;
}
for(i = 1, j = 0; i <= S[0]; i++) {
// 如果遇到空白,则略过
if(S[i] == ' ' || !isprint(S[i])) {
continue;
}
j++;
S[j] = S[i];
}
S[0] = j;
return OK;
}

View File

@@ -0,0 +1,111 @@
/*=============================
* 串的定长顺序存储表示(顺序串)
*
* 包含算法: 4.1、4.2、4.3、4.5
==============================*/
#ifndef SSTRING_H
#define SSTRING_H
#include <stdio.h>
#include <string.h> // 提供 strlen 原型
#include <ctype.h> // 提供 isprint 原型
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define MAXSTRLEN 255 // 顺序串的最大串长
/*
* 串的顺序存储类型定义
*
* 注有效元素从SString的1号单元开始存储
* SString的0号单元用来存储其长度
*/
typedef unsigned char SString[MAXSTRLEN + 1]; // 0号单元存放串的长度
/*
* ████ 提示 ████
*
* 遵循教材的书写习惯pos指示字符的位序(不是索引)从1开始计数
*/
/*
* 初始化
*
* 构造一个值为chars的串T。
*
*【注】
* 该操作属于最小操作子集
*/
Status StrAssign(SString T, const char* chars);
/*
* 清空
*
* 将串S清空。
*/
Status ClearString(SString S);
/*
* 判空
*
* 判断串S中是否包含有效数据。
*
* 返回值:
* TRUE : 串S为空
* FALSE: 串S不为空
*/
Status StrEmpty(SString S);
/*
* 计数
*
* 返回串S中元素的个数。
*
*【注】
* 该操作属于最小操作子集
*/
int StrLength(SString S);
/*
* ████████ 算法4.3 ████████
*
* 求子串
*
* 用Sub返回S[pos, pos+len-1]。
* 返回值指示是否截取成功。
*
*【注】
* 该操作属于最小操作子集
*/
Status SubString(SString Sub, SString S, int pos, int len);
/*
* 比较
*
* 比较串S和串T返回比较结果。
*
*【注】
* 该操作属于最小操作子集
*/
int StrCompare(SString S, SString T);
/*
* 复制
*
* 将串S复制到串T。
*/
Status StrCopy(SString T, SString S);
/*
* 清理字符串S中的空白包括清理不可打印字符和清理空格。
*
*【注】
* 该函数是在本章中新增的。
*/
Status ClearBlank(SString S);
#endif

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{D71FCDD5-A659-4ABD-9BBD-D534E0500789}</ProjectGuid>
<RootNamespace>My0801_BoundaryTagMethod</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(SolutionDir)\..\Status;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(SolutionDir)\..\Status\Status.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="BoundaryTagMethod-main.c" />
<ClCompile Include="BoundaryTagMethod.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="BoundaryTagMethod.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BoundaryTagMethod.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="BoundaryTagMethod-main.c">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="BoundaryTagMethod.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

View File

@@ -0,0 +1,47 @@
#include <stdio.h>
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
int main(int argc, char* argv[]) {
Space pav;
Space p[12]; // 记录申请到的内存的指针
int s[12] = {10, 20, 30, 50, 5, 15, 10, 5, 15, 15, 2, 20}; // 申请的空间大小
int i = 0;
printf("████████ InitSpace \n");
{
int max = 200; // 初值建议为20的倍数目的是打印出来可以对齐
printf("初始化包含 %d 个\"\"的内存块后,当前内存布局为:\n", max);
pav = InitSpace(max);
PrintMemoryLayout();
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ AllocBoundTag \n");
{
for(i = 0; i < 12; i++) {
printf("████ %2d> 申请 %d 个\"\"的内存后,当前内存布局为:\n", i + 1, s[i]);
p[i] = AllocBoundTag(&pav, s[i]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
printf("████████ FreeBoundTag \n");
{
// 定义一个指针回收顺序
int a[10] = {7, 3, 10, 6, 8, 5, 11, 1, 0, 4};
for(i = 0; i < 10; i++) {
printf("回收 p%d 指向的内存...\n", a[i] + 1);
FreeBoundTag(&pav, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}

View File

@@ -0,0 +1,330 @@
/*==============
* 边界标识法
*
* 包含算法: 8.1
===============*/
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
/*
* 全局变量:永远指向初始空间的头部。
* 该变量有两个作用:
* 1.用来追踪内存布局,即查看内存的使用情况。
* 2.指示内存的起始和结尾
*/
static Space av;
// 记录空间容量,用于追踪内存布局
static int len;
/*
* 初始化一块总大小为n个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head和foot。
*/
Space InitSpace(int n) {
Space space, head, foot;
// 初始化空闲内存
space = (Space) malloc(n * sizeof(WORD));
if(space == NULL) {
return NULL;
}
// 初始化头部信息
head = space;
head->llink = space; // 前驱指向自身
head->rlink = space; // 后继指向自身
head->size = n; // 空间大小已经包含了head和foot
head->tag = 0; // 标记空间空闲
// 初始化底部信息
foot = FootLoc(head); // 初始化底部指针
foot->uplink = head; // 底部域链接到本结点的头部
foot->tag = 0; // 标记空间空闲
// 记下空间的起始位置和容量
av = space;
len = n;
return space;
}
/*
* ████████ 算法8.1 ████████
*
* 边界标识法的内存分配算法
*
* 从空间pav中申请一块大小至少为n的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
* 为了使分配后的剩余块尽量均匀分布每次分配完之后都要把空间指针pav向前移动具体描述参见教材。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.为了避免空间碎片化过快这里增加了一个容差e具体含义参考教材描述。
* 3.这里申请分配n个字的空间指的是已经完成换算的空间。
* 比如用户想要容量为10个字的空间但每个空间又包含head和foot这两个字存储空间使用信息
* 因此实际上分配的空间大小应为12个字这个"12"就是n的含义。
* 教材中提到"head和foot在分配时忽略不计",这样是为了伪码书写的方便。实际写代码时,这两个空间是不能忽略的。
* 但是如果按照上面的描述把n解释为已经换算后空间而不是用户原始申请的空间
* 那么既满足了没有忽略空间,又满足了在书面效果上,跟教材伪码统一,可谓两全其美。
*/
Space AllocBoundTag(Space* pav, int n) {
Space p, f;
/*
* 增加一个判断如果换算后的空间容量小于3则直接返回。
* 因为head和foot本身就占了2个字空间用户至少申请1个字的话总申请空间至少为3个字。
*/
if(n < 3) {
printf("日志:分配失败!申请的\"\"数应当不小于3\n");
return NULL;
}
// 查找不小于n的空闲块
for(p = *pav; p && p->size < n && p->rlink != *pav; p = p->rlink) {
}
// 找不到合适的空闲块,返回空指针
if(!p || p->size < n) {
return NULL;
}
/* 至此p指向了满足条件的空闲块 */
// 让f指向该空闲块的底部
f = FootLoc(p);
// pav指向p结点的后继结点即指向下一个空闲块
*pav = p->rlink;
// 如果空闲块比申请的容量大不了多少,则需要整块分配,即不保留<=e的剩余量
if(p->size - n <= e) {
// 由于上面已经让pav指向了下一个空闲块所以如果pav与p相等说明此时只有一个空闲块了(注意空闲块链表是双循环的)
if(*pav == p) {
*pav = NULL; // 如果仅剩一个空闲块了,那么被占用后,可利用空间表变为空表
// 否则,在表中删除分配的结点
} else {
(*pav)->llink = p->llink; // 修改pav的前驱
p->llink->rlink = *pav; // 修改pav前驱的后继
/* 在上述操作中p结点的前驱与后继并没有改变这是为了方便将来的回收操作 */
}
// 更新占用块为占用状态
p->tag = f->tag = 1;
printf("日志:分配成功!申请 %d 个\"\",实际分配了 %d 个\"\"。空闲空间容量为 %d ...\n", n, n + e, AvailableSpace(*pav));
// 如果空闲块很大则从中间切割占用后面的n个字
} else {
f->tag = 1; // 修改分配块的底部标志,表示其处于占用状态
p->size -= n; // 设置剩余块大小
f = FootLoc(p); // 计算剩余块的新底部位置
f->tag = 0; // 设置新底部标志为空闲
f->uplink = p; // 新底部依然指向空闲块头部
p = f + 1; // 计算分配块的新头部位置
p->tag = 1; // 设置分配块新头部标志为占用
p->size = n; // 设置分配块的容量
// 修改分配块底部的链接(教材中缺失了此步骤)
(FootLoc(p))->uplink = p;
printf("日志:分配成功!申请并分配了 %d 个\"\"。空闲空间容量为 %d ...\n", n, AvailableSpace(*pav));
}
// 返回分配块首地址
return p;
}
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注:以下描述中的"释放块"就是指针p指向的内存
*/
void FreeBoundTag(Space* pav, Space p) {
Space h, f, q;
int Ltag, Rtag;
if(p == NULL) {
printf("日志:回收失败!内存指针为空。空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
Ltag = p == av ? 1 : (p - 1)->tag; // 块p的左邻区标志。特别注意如果块p位于内存起始处则认为它的左邻区为占用。
Rtag = (p + p->size) == (av + len) ? 1 : (p + p->size)->tag; // 块p的右邻区标志。特别注意如果块p位于内存结尾处则认为它的右邻区为占用。
/*
* 1.释放块的左、右邻区均为占用块
*
* 此时仅需要将块p插入到pav所指结点的之前即可(当然,插入到之后也是可以的)
*/
if(Ltag == 1 && Rtag == 1) {
printf("日志:\"释放块\"的容量为 %d ,且它的左、右邻区均为占用块...\n", p->size);
f = FootLoc(p);
f->uplink = p;
f->tag = 0;
p->tag = 0;
// 空闲链表为空时直接将块p变为新的独立的空闲块
if((*pav) == NULL) {
*pav = p->llink = p->rlink = p;
// 否则将块p插入到pav之前
} else {
q = (*pav)->llink;
p->rlink = *pav;
p->llink = q;
q->rlink = (*pav)->llink = p;
// 令刚释放的结点成为下次分配空间时最先查找的结点
*pav = p;
}
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 2.释放块的左邻区为空闲块,右邻区为占用块
*
* 此时需要合并左邻区与释放块
*/
if(Ltag == 0 && Rtag == 1) {
printf("日志:\"释放块\"的容量为 %d ,且它的左邻区为空闲块,右邻区为占用块...\n", p->size);
h = (p - 1)->uplink; // 左邻区的头部,这将成为合并后的新块的头部
h->size += p->size; // 左邻区容量增大
f = FootLoc(p); // 将释放块的底部做为合并后的新块的底部
f->uplink = h;
f->tag = 0;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 3.释放块的左邻区为占用块,右邻区为空闲块
*
* 此时需要合并释放块与右邻区
*/
if(Ltag == 1 && Rtag == 0) {
printf("日志:\"释放块\"的容量为 %d ,且它的左邻区为占用块,右邻区为空闲块...\n", p->size);
h = p + p->size; // 右邻区的头部
f = FootLoc(h); // 右邻区的底部,这将成为合并后的新块的底部
f->uplink = p; // 释放块的头部将作为合并后新块的头部
p->tag = 0;
p->size += h->size;
// 释放块的头部链接域要更新为与右邻区头部的链接域一致
p->llink = h->llink;
p->rlink = h->rlink;
h->llink->rlink = p;
h->rlink->llink = p;
// pav指向合并后的结点的新头部
*pav = p;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
/*
* 4.释放块的左、右邻区均为空闲块
*
* 此时需要合并左邻区、释放块、右邻区
*/
if(Ltag == 0 && Rtag == 0) {
printf("日志:\"释放块\"的容量为 %d ,且它的左、右邻区均为空闲块...\n", p->size);
h = (p - 1)->uplink; // 左邻区的头部,这将成为合并后的新块的头部
q = p + p->size; // 右邻区的头部
f = FootLoc(q); // 右邻区的底部,这将成为合并后的新块的底部
h->size += p->size + q->size; // 合并后的新块大小
f->uplink = h; // 新块底部信息也要更新
// 移除右邻区
q->rlink->llink = q->llink;
q->llink->rlink = q->rlink;
printf("日志:回收成功!空闲空间容量为 %d ...\n", AvailableSpace(*pav));
return;
}
}
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout() {
Space p;
int count;
int i;
p = av;
count = av->size;
for(i = 1; i <= count; i++) {
if(p->tag == 0) {
printf("");
} else {
printf("");
}
if(i == count && count < len) {
p = p + p->size;
count += p->size;
printf("|");
} else {
printf(" ");
}
// 每隔20个换一下行
if(i % 20 == 0) {
printf("\n");
}
}
if(len % 20 != 0) {
printf("\n");
}
}
/*
* 计算可用的空闲空间容量
*
* 注:仅限内部使用,用在日志打印中
*/
static int AvailableSpace(Space pav) {
Space p;
int count;
if(pav == NULL) {
return 0;
}
p = pav;
count = 0;
do {
count += p->size;
p = p->rlink;
} while(p != pav);
return count;
}

View File

@@ -0,0 +1,96 @@
/*==============
* 边界标识法
*
* 包含算法: 8.1
===============*/
#ifndef BOUNDARYTAGMETHOD_H
#define BOUNDARYTAGMETHOD_H
#include <stdio.h>
#include <stdlib.h>
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define e 5 // 分配空间时用到的容差
#define FootLoc(p) p+(p->size)-1 // 将p指针定位到p所指内存块的底部
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
/*
* 注:
* 教材中将llink和uplink封装在了一个联合体中个人感觉这个封装有些鸡肋。
* 一方面head和foot的公共部分其实只有tag如果真想用联合体那么应当把size和rlink也封装起来。
* 另一方面,这个封装节省的空间很有限,而且直接影响了代码的可读性。
* 这里只是教学代码,而不是真实的系统代码,所以空间考虑在其次,原理展示为首要任务。
* 此外,教材中的伪码中也并没有考虑这个联合体,而是直接进行操作的。
* 综上所述,这里去除了教材中的联合体结构。
* 如想观察带有联合体的写法可以参考CFree分支的代码。
*/
int tag; // 块标志0空闲1占用头部和尾部均有
struct WORD* llink; // 头部域,指向前驱结点
struct WORD* rlink; // 头部域,指向后继结点
int size; // 头部域,块大小
struct WORD* uplink; // 底部域,指向本结点头部
} WORD;
typedef WORD* Space; // Space指向可利用空间的指针类型
/*
* 初始化一块大小为n个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head和foot。
*/
Space InitSpace(int n);
/*
* ████████ 算法8.1 ████████
*
* 边界标识法的内存分配算法
*
* 从空间pav中申请一块大小至少为n的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
* 为了使分配后的剩余块尽量均匀分布每次分配完之后都要把空间指针pav向前移动具体描述参见教材。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.为了避免空间碎片化过快这里增加了一个容差e具体含义参考教材描述。
* 3.这里申请分配n个字的空间指的是已经完成换算的空间。
* 比如用户想要容量为10个字的空间但每个空间又包含head和foot这两个字存储空间使用信息
* 因此实际上分配的空间大小应为12个字这个"12"就是n的含义。
* 教材中提到"head和foot在分配时忽略不计",这样是为了伪码书写的方便。实际写代码时,这两个空间是不能忽略的。
* 但是如果按照上面的描述把n解释为已经换算后空间而不是用户原始申请的空间
* 那么既满足了没有忽略空间,又满足了在书面效果上,跟教材伪码统一,可谓两全其美。
*/
Space AllocBoundTag(Space* pav, int n);
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void FreeBoundTag(Space* pav, Space p);
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout();
/*
* 计算可用的空闲空间容量
*
* 注:仅限内部使用,用在日志打印中
*/
static int AvailableSpace(Space pav);
#endif

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{6485E7FA-6038-4C6F-A91F-3B1D5E8B532D}</ProjectGuid>
<RootNamespace>My0802_BuddySystem</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(SolutionDir)\..\Status;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(SolutionDir)\..\Status\Status.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="BuddySystem-main.c" />
<ClCompile Include="BuddySystem.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="BuddySystem.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="BuddySystem.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="BuddySystem-main.c">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="BuddySystem.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

View File

@@ -0,0 +1,44 @@
#include <stdio.h>
#include "BuddySystem.h" //**▲08 动态存储管理**//
int main(int argc, char* argv[]) {
FreeList avail;
WORD* p[8]; // 记录申请到的内存的指针
int s[8] = {4, 5, 6, 7, 1, 5, 3, 9}; // 申请的空间大小
int i;
printf("████████ InitSpace \n");
{
printf("初始化一个内存块...\n");
InitSpace(avail);
PrintMemoryLayout();
printf("\n");
}
PressEnterToContinue(debug);
printf("████████ AllocBuddy \n");
{
for(i = 0; i < 8; i++) {
printf("申请大小为 %d 个字的内存块...\n", s[i]);
p[i] = AllocBuddy(avail, s[i]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
printf("████████ FreeBuddy \n");
{
// 定义一个指针回收顺序
int a[8] = {2, 0, 5, 7, 1, 4, 3, 6};
for(i = 0; i < 8; i++) {
FreeBuddy(avail, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}

View File

@@ -0,0 +1,283 @@
/*==============
* 伙伴系统
*
* 包含算法: 8.2
===============*/
#include "BuddySystem.h"
// 记录内存的起始地址,在计算伙伴块时候需要用到
WORD* start;
/*
* 初始化一块大小为2^M个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head。
*/
void InitSpace(FreeList avail) {
int k;
WORD* r;
// 遍历M+1个元素
for(k = 0; k <= M; k++) {
avail[k].nodesize = (int) pow(2, k);
avail[k].first = NULL;
}
r = (WORD*) malloc((int) pow(2, M) * sizeof(WORD));
if(r == NULL) {
exit(OVERFLOW);
}
// 设置头部信息
r->llink = r->rlink = r;
r->tag = 0;
r->kval = M;
avail[M].first = r;
start = r;
}
/*
* ████████ 算法8.2 ████████
*
* 伙伴系统的内存分配算法
*
* 从空间avail中申请一块大小至少为n(原始值)的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.这里申请分配n个字的空间指的是用户申请的原始空间。
* 实际在申请时还要考虑到每个块前面有1个字的head信息即经过换算后实际需要申请(n+1)个字。
* 这里的n与算法8.1里面的n含义正好相反需要注意。
*/
WORD* AllocBuddy(FreeList avail, int n) {
int k, i;
WORD* pa, * pre, * suc, * pi;
/*
* 增加一个判断如果换算后的空间容量小于1则直接返回。
*/
if(n < 1) {
printf("日志:分配失败!申请的\"\"数应当不小于1\n");
return NULL;
}
// 查找不小于n的空闲块
for(k = 0; k <= M && (avail[k].nodesize < n + 1 || !avail[k].first); k++) {
}
// 找不到合适的空闲块,返回空指针
if(k > M) {
printf("日志:分配失败!没有足够的空闲块\n");
return NULL;
}
pa = avail[k].first; // 指向可分配子表的第一个结点
pre = pa->llink; // 分别记下前驱和后继
suc = pa->rlink;
// 如果此处仅有一个空闲块,则分配后该子表变为空
if(pa == suc) {
avail[k].first = NULL;
// 否则,从链表头部摘下一个可用的空闲块,并将子表头指针指向下一个空闲块
} else {
pre->rlink = suc;
suc->llink = pre;
avail[k].first = suc;
}
/*
* 从k-1处开始逆向遍历FreeList数组向其中填充剩余的空闲块。
* 剩余的空闲块是对剩余可用空间的拆分。
*
* 这里用到一个公式2^m-2^n = 2^n+2^(n+1)+...+2^(m-1)
* 比如初始容量为2^16此时总共申请1500个字那么需要分配一块2^11的空闲块给它。
* 分配完之后剩余的容量为2^16-2^11 = 2^11+2^12+2^13+2^14+2^15。
* 这些剩余容量可以拆分为5个空闲块分别存储到15、14、13、12、11这五个索引处。
*/
for(i = 1; k - i >= 0 && avail[k - i].nodesize >= n + 1; i++) {
pi = pa + (int) pow(2, k - i); // 每次将pi指向剩余空间的后一半
pi->rlink = pi->llink = pi; // 初始化pi的前驱和后继
pi->tag = 0; // 标记为空闲块
pi->kval = k - i; // 设置该块的容量标志真实容量为2^(k-i)
avail[k - i].first = pi;
/*
* 注:
* 上面分解出来的pi直接添加到了avail中并没有考虑同位置处会不会有别的容量相同的空闲块。
* 这里不需要考虑的原因是如果同位置处已经存在别的容量相同的空闲块,
* 那么这里根本不需要分解剩余空间,换句话说,连这个循环都进不来。
* 只要进来这个循环,说明目标位置处已经为空了,没有找到合适的空闲块,所以这才进一步向高游标处寻找空闲块。
*/
}
// 最后剩下的最靠前的空间就是需要分配的空间(这里没有设置pa的前驱和后继因为没必要)
pa->tag = 1;
pa->kval = k - (--i);
printf("日志:分配成功!用户申请 %d 个字,系统申请 %d 个字,实际分配 %d 个字\n", n, n + 1, (int) pow(2, pa->kval));
return pa;
}
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void FreeBuddy(FreeList avail, WORD* p) {
int k;
WORD* r;
WORD* buddy = Buddy(p);
if(p == NULL) {
return;
}
/*
* 这里将p结点简单地插入到avail中包含三种情形
* 1.伙伴块非空闲
* 2.伙伴空闲但是伙伴的大小跟p的大小不一致说明伙伴还没拼合好
* 3.p拼接成了最后一个最大的空闲块
*/
if(buddy->tag == 1 || buddy->kval != p->kval || p->kval == M) {
for(k = 0; k <= M && k < p->kval; k++) {
// 查找p结点应当进入的插槽
}
// 找到插槽,采用头插法将空闲块插入到目标插槽
if(k <= M && k == p->kval) {
p->tag = 0;
if(avail[k].first == NULL) {
p->llink = p->rlink = p;
} else {
p->llink = avail[k].first->llink;
p->rlink = avail[k].first;
p->llink->rlink = p;
p->rlink->llink = p;
}
avail[k].first = p;
printf("日志:回收成功![%d, (2^%d)]进入插槽 %d 的空闲块链表上\n", (int) (p - start), k, k);
}
// 如果伙伴块是空闲的,此时应当进行合并操作
} else {
for(k = 0; k <= M && k < p->kval; k++) {
// 查找伙伴块所在的插槽
}
// 找到插槽,将伙伴块从空闲块链表中摘下来
if(k <= M && k == p->kval) {
// 伙伴在链表第一个位置
if(avail[k].first == buddy) {
buddy->rlink->llink = buddy->llink;
buddy->llink->rlink = buddy->rlink;
avail[k].first = buddy->rlink;
// 伙伴在中间位置
} else {
for(r = avail[k].first; r->rlink != buddy; r = r->rlink) {
// 查找伙伴r指向伙伴的前驱
}
r->rlink = buddy->rlink;
buddy->rlink->llink = r;
}
printf("日志:合并成功![%d, (2^%d)]和[%d, (2^%d)]合并成了", (int) (p - start), k, (int) (buddy - start), k);
// 合并之前,需要确定哪个伙伴靠前
if(p < buddy) {
p->tag = 0;
} else {
p = buddy;
}
p->kval = k + 1; // 指数增一后,即完成合并
printf("[%d, (2^%d)]\n", (int) (p - start), k + 1);
// 出现新的空闲块之后,要进入递归,查看该空闲块是否也存在空闲伙伴
FreeBuddy(avail, p);
}
}
}
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout() {
int i, count, total;
WORD* p;
printf("|");
p = start;
count = (int) pow(2, p->kval);
for(i = 1; i <= count; i++) {
if(p->tag == 0) {
printf("_");
} else {
printf("*");
}
// 进入到下一个块
if(i == count && count < (int) pow(2, M)) {
p = start + count;
count += (int) pow(2, p->kval);
printf("|");
}
}
printf("|\n");
}
/*
* 查找块p的伙伴
*
* 将一个空闲块对半分裂后,会生成的两个小空闲块,这两个小空闲块互为伙伴。
*
* 计算伙伴的算法为:
* 对于起始地址为p大小为2^k的内存块
* 1.若 p MOD 2^(k+1) == 0 ,则p的伙伴块的起始地址为p+2^k
* 2.若 p MOD 2^(k+1) == 2^k ,则p的伙伴块的起始地址为p-2^k。
*
* 注:仅限内部使用,用在回收算法中
*/
static WORD* Buddy(WORD* p) {
long s, m, n;
if(p == NULL) {
return NULL;
}
// start是整个空闲块的绝对起始地址s是p在伙伴系统中的绝对地址从0开始
s = p - start;
if(s < 0) {
return NULL;
}
m = (long) pow(2, p->kval);
n = (long) pow(2, p->kval + 1);
if(s % n == 0) {
return p + m;
}
if(s % n == m) {
return p - m;
}
return NULL;
}

View File

@@ -0,0 +1,89 @@
/*==============
* 伙伴系统
*
* 包含算法: 8.2
===============*/
#ifndef BUDDYSYSTEM_H
#define BUDDYSYSTEM_H
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define M 6 // 假设总空间大小为2^M个字子表个数为M+1
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
struct WORD* llink; // 头部域的前驱指针
struct WORD* rlink; // 头部域的后继指针
int tag; // 头部域的块标志0:空闲1:占用
int kval; // 指示块的大小比如其值为K时表示该块的大小为2^K
} WORD;
// 表头向量类型
typedef struct HeadNode {
int nodesize; // 该链表的空闲块的大小
WORD* first; // 该链表的表头指针
} FreeList[M + 1];
/*
* 初始化一块大小为2^M个字的内存并返回指向该内存的起点的指针
* 注返回的初始内存已经包含了head。
*/
void InitSpace(FreeList avail);
/*
* ████████ 算法8.2 ████████
*
* 伙伴系统的内存分配算法
*
* 从空间avail中申请一块大小至少为n(原始值)的空间并返回指向申请到的空间的指针。如果分配失败则返回NULL。
*
* 注:
* 1.这里采用首次拟合法,即一遇到满足条件的内存块就进行分配操作。
* 2.这里申请分配n个字的空间指的是用户申请的原始空间。
* 实际在申请时还要考虑到每个块前面有1个字的head信息即经过换算后实际需要申请(n+1)个字。
* 这里的n与算法8.1里面的n含义正好相反需要注意。
*/
WORD* AllocBuddy(FreeList avail, int n);
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void FreeBuddy(FreeList avail, WORD* p);
/*
* 打印内存布局,查看当前内存的使用情况
* 注:仅限内部测试使用
*/
void PrintMemoryLayout();
/*
* 查找块p的伙伴
*
* 将一个空闲块对半分裂后,会生成的两个小空闲块,这两个小空闲块互为伙伴。
*
* 计算伙伴的算法为:
* 对于起始地址为p大小为2^k的内存块
* 1.若 p MOD 2^(k+1) == 0 ,则p的伙伴块的起始地址为p+2^k
* 2.若 p MOD 2^(k+1) == 2^k ,则p的伙伴块的起始地址为p-2^k。
*
* 注:仅限内部使用,用在回收算法中
*/
static WORD* Buddy(WORD* p);
#endif

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{66EA62B4-2F77-4BE6-AA24-C713EB7771DD}</ProjectGuid>
<RootNamespace>My0803_GarbageCollection</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>MultiByte</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<IncludePath>$(SolutionDir)\..\Status;$(IncludePath)</IncludePath>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>Disabled</Optimization>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<AdditionalDependencies>$(SolutionDir)\..\Status\Status.lib;%(AdditionalDependencies)</AdditionalDependencies>
<SubSystem>Console</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<Optimization>MaxSpeed</Optimization>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="GarbageCollection-main.c" />
<ClCompile Include="GarbageCollection.c" />
<ClCompile Include="GList-HT.c" />
<ClCompile Include="SString.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="GarbageCollection.h" />
<ClInclude Include="GList-HT.h" />
<ClInclude Include="SString.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="源文件">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="头文件">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
<Filter Include="资源文件">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="GarbageCollection.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GarbageCollection-main.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="GList-HT.c">
<Filter>源文件</Filter>
</ClCompile>
<ClCompile Include="SString.c">
<Filter>源文件</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="GarbageCollection.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="GList-HT.h">
<Filter>头文件</Filter>
</ClInclude>
<ClInclude Include="SString.h">
<Filter>头文件</Filter>
</ClInclude>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
</Project>

View File

@@ -0,0 +1,201 @@
/*============================
* 广义表的头尾链表存储表示
*
* 包含算法: 5.5、5.6、5.7、5.8
=============================*/
#include "GList-HT.h" //**▲05 数组和广义表**//
/*
* 初始化
*
* 初始化空的广义表长度为0深度为1。
*
*【注】
* 需要对每一层去掉括号考察
*/
Status InitGList(GList* L) {
if(L == NULL) {
return ERROR;
}
*L = NULL;
return OK;
}
/*
* ████████ 算法5.7 ████████
*
* 创建
*
* 由字符串S创建广义表L。
*/
Status CreateGList(GList* L, SString S) {
SString emp; // 代表空广义表的字符串
SString hsub, sub;
GList p, q;
if(L == NULL) {
return ERROR;
}
// 清理字符串S中的空白包括清理不可打印字符和清理空格
ClearBlank(S);
if(StrEmpty(S)) {
return ERROR;
}
StrAssign(emp, "()");
/*
* 如果输入串为(),则代表需要创建空的广义表
*
*【注】
* 教材这里的代码是有问题的。
* StrCompare的返回值指示的是两个字符串的大小而不是指示两个字符串是否相等。
* 如果给定的S与()相等返回值应当是0。
*/
if(!StrCompare(S, emp)) {
*L = NULL;
} else {
*L = (GList) malloc(sizeof(GLNode));
if(*L == NULL) {
exit(OVERFLOW);
}
// 初始化标记为0意思是该结点未访问
(*L)->mark = 0;
// 创建原子
if(StrLength(S) == 1) {
(*L)->tag = Atom;
(*L)->Node.atom = S[1];
} else {
(*L)->tag = List;
p = *L;
// 去掉最外层括号
SubString(sub, S, 2, StrLength(S) - 2);
// 重复建n个子表
do {
// 从sub中分离出表头串hsub分离完成后sub也会发生变化
sever(hsub, sub);
// 递归创建广义表
CreateGList(&(p->Node.ptr.hp), hsub);
q = p;
// 如果表尾不为空,需要继续处理表尾
if(!StrEmpty(sub)) {
p = (GList) malloc(sizeof(GLNode));
if(p == NULL) {
exit(OVERFLOW);
}
// 初始化标记为0意思是该结点未访问
p->mark = 0;
p->tag = List;
q->Node.ptr.tp = p;
}
} while(!StrEmpty(sub));
q->Node.ptr.tp = NULL;
}
}
return OK;
}
/*
* 图形化输出
*
* 带括号输出广义表L。
*/
void PrintGList(GList L) {
Print(L, Head);
printf("\n");
}
/*
* 图形化输出的内部实现mark是图形化输出标记。
*/
static void Print(GList L, Mark mark) {
// L为空
if(L == NULL) {
if(mark == Head) {
printf("(");
}
printf(")");
// L不为空时
} else {
// 对于原子结点,输出原子
if(L->tag == Atom) {
printf("%c", L->Node.atom);
// 对于子表结点,要对表头、表尾分别讨论
} else {
if(mark == Head) {
printf("(");
} else {
printf(",");
}
Print(L->Node.ptr.hp, Head);
Print(L->Node.ptr.tp, Tail);
}
}
}
/*
* ████████ 算法5.8 ████████
*
* 将非空串str分割成两部分hsub为第一个','之前的子串str为第一个','之后的子串。
*
*【注】
* 1.这里假设字符串str输入正确其中无空白符号且str【已经脱去最外层括号】。
* 2.分离完成后str也会发生变化
*/
static void sever(SString hstr, SString str) {
int i, k, n;
SString ch;
n = StrLength(str);
i = 0; // 遍历字符串时的游标
k = 0; // 标记遇到的未配对括号数量
do {
++i;
// 截取str第一个字符
SubString(ch, str, i, 1);
if(ch[1] == '(') {
++k;
}
if(ch[1] == ')') {
--k;
}
} while(i < n && (ch[1] != ',' || k != 0));
// 如果存在多个广义表结点
if(i < n) {
SubString(hstr, str, 1, i - 1);
SubString(str, str, i + 1, n - i);
// 只有一个广义表结点
} else {
StrCopy(hstr, str);
ClearString(str);
}
}

View File

@@ -0,0 +1,96 @@
/*============================
* 广义表的头尾链表存储表示
*
* 包含算法: 5.5、5.6、5.7、5.8
=============================*/
#ifndef GLIST_HT_H
#define GLIST_HT_H
#include <stdio.h>
#include <stdlib.h> // 提供 malloc、realloc、free、exit 原型
#include "Status.h" //**▲01 绪论**//
#include "SString.h" //**▲04 串**//
/* 原子元素类型 */
typedef char AtomType;
/*
* 广义表结点标记
*
* Atom-0原子结点
* List-1表结点
*/
typedef enum { Atom, List } ElemTag;
/* 广义表(头尾链表存储表示)类型定义 */
typedef struct GLNode {
int mark; // 为第8章第5节新增的变量用来给广义表打上遍历标记会在创建中初始化为0
ElemTag tag; // 公共标记,用于区分原子结点和表结点
// 原子结点和表结点的联合部分
union {
AtomType atom; // atom是原子结点的值域AtomType由用户定义
struct {
struct GLNode* hp; // 指向表头
struct GLNode* tp; // 指向表尾
} ptr; // 表结点的指针域
} Node;
} GLNode;
/* 广义表类型 */
typedef GLNode* GList;
/*
* 图形化输出标记
*
* Head代表广义表指针来自表头
* Tail代表广义表指针来自表尾
*/
typedef enum { Head, Tail } Mark;
/*
* 初始化
*
* 初始化空的广义表长度为0深度为1。
*
*【注】
* 需要对每一层去掉括号考察
*/
Status InitGList(GList* L);
/*
* ████████ 算法5.7 ████████
*
* 创建
*
* 由字符串S创建广义表L。
*/
Status CreateGList(GList* L, SString S);
/*
* 图形化输出
*
* 带括号输出广义表L。
*/
void PrintGList(GList L);
/*
* 图形化输出的内部实现mark是图形化输出标记。
*/
static void Print(GList L, Mark mark);
/*
* ████████ 算法5.8 ████████
*
* 将非空串str分割成两部分hsub为第一个','之前的子串str为第一个','之后的子串。
*
*【注】
* 1.这里假设字符串str输入正确其中无空白符号且str【已经脱去最外层括号】。
* 2.分离完成后str也会发生变化
*/
static void sever(SString hstr, SString str);
#endif

Some files were not shown because too many files have changed in this diff Show More