💡 <08 动态存储管理>习题代码

This commit is contained in:
康建伟
2020-04-16 11:34:18 +08:00
parent 81db65cd68
commit 88c08be0dc
86 changed files with 8628 additions and 0 deletions

View File

@@ -0,0 +1,126 @@
#include <stdio.h>
#include "Status.h"
#include "SqStack.h"
/* 宏定义 */
#define MAXSIZE 200 // 内存总大小
/* 空闲空间类型定义 */
typedef struct {
void* base; // 可用空间内存的起始地址
int size; // 空闲空间大小(按字节计数)
SqStack block; // 已经使用的内存块大小,先申请的内存信息先入栈
} Space;
// 初始化内存空间及内存信息栈
void InitSpace(Space* space);
// 打印内存布局,查看当前内存的使用情况
void PrintMemoryLayout(Space space);
// 申请大小为n的内存块返回申请到的内存起始地址
void* Algo_8_11(Space* space, int n);
// 回收内存,遵循"最后分配者最先释放"的原则
Status Algo_8_12(Space* space);
int main(int argc, char* argv[]) {
Space space;
int s[5] = {50, 50, 150, 100, 50}; // 定义内存申请数量
int i;
// 初始化内存
InitSpace(&space);
PrintMemoryLayout(space);
printf("\n");
// 申请/释放内存
for(i = 0; i < 5; i++) {
printf("申请 %d 个内存后...\n", s[i]);
Algo_8_11(&space, s[i]);
PrintMemoryLayout(space);
printf("\n");
}
return 0;
}
// 初始化一块空闲空间
void InitSpace(Space* space) {
// 申请MAX字节的空间(假设总能初始化成功)
space->base = malloc(MAXSIZE);
space->size = MAXSIZE;
// 初始化内存信息栈
InitStack(&(space->block));
}
// 打印内存布局,查看当前内存的使用情况
void PrintMemoryLayout(Space space) {
int i;
for(i = 1; i <= MAXSIZE; i++) {
if(i <= space.size) {
printf("");
} else {
printf("");
}
// 预定每隔20个换一下行
if(i % 20 == 0) {
printf("\n");
}
}
if(MAXSIZE % 20 != 0) {
printf("\n");
}
}
// 申请大小为n的内存块返回申请到的内存起始地址
void* Algo_8_11(Space* space, int n) {
/*
* 如果可用的内存数量少于申请的内存数量,
* 则需要释放最近申请使用的内存以腾出空间。
*/
while(space->size < n && Algo_8_12(space) == OK) {
// 直到空闲内存满足申请要求,或者全部占用内存已经释放,才会退出循环
}
// 如果没有足够的内存可用,则返回空指针
if(space->size < n) {
return NULL;
}
// 可用空间减小
space->size -= n;
// 记下成功申请到的内存
Push(&(space->block), n);
// 返回申请到的内存起始地址
return space->base + space->size;
}
// 回收内存,遵循"最后分配者最先释放"的原则
Status Algo_8_12(Space* space) {
int e;
// 已经没有可释放内存时返回ERROR
if(StackEmpty(space->block)) {
return ERROR;
}
// 将最后申请的内存释放掉
Pop(&(space->block), &e);
// 可用空间增大
space->size += e;
return OK;
}

View File

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

View File

@@ -0,0 +1,90 @@
/*=========================
* 栈的顺序存储结构(顺序栈)
==========================*/
#include "SqStack.h" //**▲03 栈和队列**//
/*
* 初始化
*
* 构造一个空栈。初始化成功则返回OK否则返回ERROR。
*/
Status InitStack(SqStack* S) {
if(S == NULL) {
return ERROR;
}
(*S).base = (SElemType*) malloc(STACK_INIT_SIZE * sizeof(SElemType));
if((*S).base == NULL) {
exit(OVERFLOW);
}
(*S).top = (*S).base;
(*S).stacksize = STACK_INIT_SIZE;
return OK;
}
/*
* 判空
*
* 判断顺序栈中是否包含有效数据。
*
* 返回值:
* TRUE : 顺序栈为空
* FALSE: 顺序栈不为空
*/
Status StackEmpty(SqStack S) {
if(S.top == S.base) {
return TRUE;
} else {
return FALSE;
}
}
/*
* 入栈
*
* 将元素e压入到栈顶。
*/
Status Push(SqStack* S, SElemType e) {
if(S == NULL || (*S).base == NULL) {
return ERROR;
}
// 栈满时,追加存储空间
if((*S).top - (*S).base >= (*S).stacksize) {
(*S).base = (SElemType*) realloc((*S).base, ((*S).stacksize + STACKINCREMENT) * sizeof(SElemType));
if((*S).base == NULL) {
exit(OVERFLOW); // 存储分配失败
}
(*S).top = (*S).base + (*S).stacksize;
(*S).stacksize += STACKINCREMENT;
}
// 进栈先赋值,栈顶指针再自增
*(S->top++) = e;
return OK;
}
/*
* 出栈
*
* 将栈顶元素弹出并用e接收。
*/
Status Pop(SqStack* S, SElemType* e) {
if(S == NULL || (*S).base == NULL) {
return ERROR;
}
if((*S).top == (*S).base) {
return ERROR;
}
// 出栈栈顶指针先递减,再赋值
*e = *(--(*S).top);
return OK;
}

View File

@@ -0,0 +1,59 @@
/*=========================
* 栈的顺序存储结构(顺序栈)
==========================*/
#ifndef SQSTACK_H
#define SQSTACK_H
#include <stdio.h>
#include <stdlib.h> // 提供malloc、realloc、free、exit原型
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define STACK_INIT_SIZE 100 // 顺序栈存储空间的初始分配量
#define STACKINCREMENT 10 // 顺序栈存储空间的分配增量
/* 顺序栈元素类型定义 */
typedef int SElemType;
// 顺序栈元素结构
typedef struct {
SElemType* base; // 栈底指针
SElemType* top; // 栈顶指针
int stacksize; // 当前已分配的存储空间,以元素为单位
} SqStack;
/*
* 初始化
*
* 构造一个空栈。初始化成功则返回OK否则返回ERROR。
*/
Status InitStack(SqStack* S);
/*
* 判空
*
* 判断顺序栈中是否包含有效数据。
*
* 返回值:
* TRUE : 顺序栈为空
* FALSE: 顺序栈不为空
*/
Status StackEmpty(SqStack S);
/*
* 入栈
*
* 将元素e压入到栈顶。
*/
Status Push(SqStack* S, SElemType e);
/*
* 出栈
*
* 将栈顶元素弹出并用e接收。
*/
Status Pop(SqStack* S, SElemType* e);
#endif

View File

@@ -0,0 +1,65 @@
#include <stdio.h>
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void Algo_8_13(Space* pav, Space p);
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);
Algo_8_13(&pav, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void Algo_8_13(Space* pav, Space p) {
FreeBoundTag(pav, p);
}

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(08.13 BoundaryTagMethod.h BoundaryTagMethod.c 08.13.c)
# 链接公共库
target_link_libraries(08.13 Scanf_lib)
# 记录要拷贝到*.exe目录下的资源文件
file(GLOB TestData TestData*.txt)
# 将资源文件拷贝到*.exe目录下不然无法加载
file(COPY ${TestData} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,66 @@
#include <stdio.h>
#include "BuddySystem.h" //**▲08 动态存储管理**//
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void Algo_8_14(FreeList avail, WORD* p);
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++) {
Algo_8_14(avail, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void Algo_8_14(FreeList avail, WORD* p) {
FreeBuddy(avail, p);
}

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(08.14 BuddySystem.h BuddySystem.c 08.14.c)
# 链接公共库
target_link_libraries(08.14 Scanf_lib)
# 记录要拷贝到*.exe目录下的资源文件
file(GLOB TestData TestData*.txt)
# 将资源文件拷贝到*.exe目录下不然无法加载
file(COPY ${TestData} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -0,0 +1,85 @@
#include <stdio.h>
#include "MemoryManager.h"
/*
* 内存整理
*
* 将空闲块收集起来,链接成一条空闲块链表。
*/
void Algo_8_15(Space* space);
int main(int argc, char** argv) {
Space space;
int i;
int a[11] = {10, 20, 30, 15, 20, 5, -1, -3, 0, 15, 10};
WORD* p[11];
InitSpace(&space);
printf("初始内存布局:\n");
PrintMemoryLayout(space);
/*
* 注:
* 申请a[4]时仅剩25个"字"当前容差是5因此本次会直接分配20+5=25个"字"
* 申请a[5]时,已经没有可用的字,所以申请失败
* 申请a[10]时,处在容差范围内,会整块分配
*/
for(i = 0; i < 11; i++) {
if(a[i] > 0) {
printf("+ 申请 %d 个字的存储\n", a[i]);
p[i] = AllocMemory(&space, a[i]);
} else {
printf("- 释放 %d 个字的存储\n", a[-a[i]]);
FreeMemory(&space, p[-a[i]]);
}
PrintMemoryLayout(space);
if(i == 8) {
printf("内存整理...\n");
Algo_8_15(&space);
PrintMemoryLayout(space);
}
}
}
/*
* 内存整理
*
* 将空闲块收集起来,链接成一条空闲块链表。
*/
void Algo_8_15(Space* space) {
WORD* p, * q;
space->avail = NULL;
q = NULL;
// 遍历整个堆
for(p = space->lowbound; p < space->highbound; p += p->cellsize) {
if(p->tag == 1) {
continue;
}
// 先把后继置空
p->next = NULL;
// 遇到了第一个空闲块
if(space->avail == NULL) {
space->avail = p;
q = p;
continue;
}
// 如果两个空闲块地址不相邻,则进行链接
if(q + q->cellsize != p) {
q->next = p;
// 对于地址相邻的空闲块,要合并
} else {
q->cellsize += p->cellsize;
}
}
}

View File

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

View File

@@ -0,0 +1,131 @@
/*=======================
* 存储管理适用于习题8.15
========================*/
#include "MemoryManager.h"
// 初始化一片可用的存储
void InitSpace(Space* space) {
space->avail = (WORD*) malloc(MAXSIZE * sizeof(WORD));
space->avail->tag = 0;
space->avail->cellsize = MAXSIZE;
space->avail->next = NULL;
space->lowbound = space->avail;
space->highbound = space->avail + MAXSIZE;
}
// 分配大小为n的内存并返回指向该内存的虚拟地址
WORD* AllocMemory(Space* space, int n) {
WORD* p, * q;
if(space->avail == NULL) {
return NULL;
}
p = space->avail;
q = NULL;
// 如果第一个空闲块不满足条件
if(p->cellsize < n) {
// 查找满足条件的空闲内存
while(p != NULL && p->cellsize < n) {
q = p;
p = p->next;
}
}
// 没有符合条件的空闲内存
if(p == NULL) {
return NULL;
}
// 在容差范围内,则整块分配
if(p->cellsize - e <= n) {
if(q == NULL) {
space->avail = p->next;
} else {
q->next = p->next;
}
// 分割空闲块
} else {
if(q == NULL) {
space->avail += n;
space->avail->tag = 0;
space->avail->cellsize = p->cellsize - n;
space->avail->next = p->next;
} else {
q->next = p + n;
q->next->tag = 0;
q->next->cellsize = p->cellsize - n;
}
p->cellsize = n;
}
// 标记为占用
p->tag = 1;
return p;
}
// 释放指针p处的内存
void FreeMemory(Space* space, WORD* p) {
p->tag = 0; // 简单地标记为空闲,以待后续整理
}
// 打印内存布局
void PrintMemoryLayout(Space space) {
int i, count;
WORD* p;
// 遍历所有块
for(p = space.lowbound; p < space.highbound; p += p->cellsize) {
if(p->tag == 0) {
p->flag = -1; // 暂时标记为"不可用"的空闲块
} else {
p->flag = 1; // 标记为占用
}
}
// 遍历空闲块链表
for(p = space.avail; p != NULL; p = p->next) {
p->flag = 0; // 更新为"可用"的空闲块
}
count = 0;
for(p = space.lowbound; p < space.highbound; p += p->cellsize) {
for(i = 0; i < p->cellsize; i++) {
switch(p->flag) {
case -1:
printf("");
break;
case 0:
printf("");
break;
case 1:
printf("");
break;
default:
printf("错误的标记!\n");
return;
}
count++;
// 每隔20个字换一下行
if(count % 20 == 0) {
printf("\n");
}
}
}
if(count % 20 != 0) {
printf("\n");
}
printf("\n");
}

View File

@@ -0,0 +1,57 @@
/*=======================
* 存储管理适用于习题8.15
========================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef MEMORYMANAGER_H
#define MEMORYMANAGER_H
/* 宏定义 */
#define MAXSIZE 100 // 内存总容量
#define e 5 // 分配空间时用到的容差
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
int tag; // 块标志0空闲1占用
int cellsize; // 当前块的大小
struct WORD* next; // 指向下一个空闲块
/*
* 使用标记0代表空闲1代表占用-1代表虽然空闲但是不在空闲链表中。
* 这个标记仅用来测试打印功能,并没有其它实质性的用处。
*/
int flag;
} WORD;
// "堆"空间
typedef struct {
struct WORD* lowbound; // 空间下界
struct WORD* highbound; // 空间上界
struct WORD* avail; // 指向可用空间链表
} Space;
// 初始化一片可用的存储
void InitSpace(Space* space);
// 分配大小为n的内存并返回指向该内存的虚拟地址
WORD* AllocMemory(Space* space, int n);
// 释放指针p处的内存
void FreeMemory(Space* space, WORD* p);
// 打印内存布局
void PrintMemoryLayout(Space space);
#endif

View File

@@ -0,0 +1,441 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MemoryManager.h"
/*
* 存储紧缩算法
*
* 该算法的实现过程与存储管理方案息息相关。
* 此处的紧缩算法是建立在自己实现的MemoryManager之上的。
* 如果有别的存储管理方案,那么紧缩算法也需要随之改变。
*
* 具体步骤的描述参见教材。
*/
void Algo_8_16(Space* space);
// 依照内存记录的首地址对内存记录进行排序时用到的比较器,该方法仅限内部使用
static int qsortCompar(const void* r1, const void* r2);
// 查找一条内存记录的索引该内存记录拥有旧地址oldAddr该方法仅限内部使用
static int search(Space space, int oldAddr);
// 对习题8.10给出的数据进行测试
void Test_8_10(Space* space, char* path);
// 使用自定义的测试数据,这里还可以测试用户变量的变更情况
void TestCustom(Space* space, int* p[]);
int main(int argc, char** argv) {
// printf("=== 使用习题8.10中的测试数据 ===\n\n");
// {
// Space space;
//
// // 初始化一片可用的存储
// InitSpace(&space);
// printf("初始内存布局:\n");
// PrintMemoryLayout(space);
//
// // 测试习题8.10的数据
// Test_8_10(&space, "TestData_8_10.md");
// printf("习题8.10中的内存布局:\n");
// PrintMemoryLayout(space);
//
// // 存储紧缩
// Algo_8_16(&space);
// printf("进行存储紧缩后的内存布局:\n");
// PrintMemoryLayout(space);
// }
printf("=== 使用自定义的测试数据 ===\n\n");
{
Space space;
int* p[10]; // 用户变量表(虚拟地址)
char* data = (char*)malloc(sizeof(char));
// 初始化一片可用的存储
InitSpace(&space);
printf("初始内存布局:\n");
PrintMemoryLayout(space);
// 自定义数据
TestCustom(&space, p);
// 存储紧缩
Algo_8_16(&space);
printf("进行存储紧缩后的内存布局:\n");
PrintMemoryLayout(space);
printf("对照内存布局,验证用户变量是否已经同步更新:\n");
memmove(data, getPtr(space, p[9]), 1);
printf("p10 = %d\n", *data);
memmove(data, getPtr(space, p[6]), 1);
printf("p7 = %d\n", *data);
memmove(data, getPtr(space, p[7]), 1);
printf("p8 = %d\n", *data);
memmove(data, getPtr(space, p[5]), 1);
printf("p6 = %d\n", *data);
memmove(data, getPtr(space, p[8]), 1);
printf("p9 = %d\n", *data);
}
}
/*
* 存储紧缩算法
*
* 该算法的实现过程与存储管理方案息息相关。
* 此处的紧缩算法是建立在自己实现的MemoryManager之上的。
* 如果有别的存储管理方案,那么紧缩算法也需要随之改变。
*
* 具体步骤的描述参见教材。
*/
void Algo_8_16(Space* space) {
Record* r;
int len;
int i, j, k, count;
int busy, free;
int* oldPtr; // 记录旧的虚拟地址
// 当前包含的内存使用记录条数
len = ListLength(space->recordList);
/* 0. 对内存记录表进行排序,这有利于后续的内存数据迁移 */
qsort(space->recordList.elem, len, sizeof(ElemType), qsortCompar);
// printf("日志0. 对内存记录进行排序(至此,只完成了对首地址的顺序):\n");
// PrintMemoryLayout(*space);
/* 1. 确定经过存储紧缩后,各内存块的新地址,换句话说,经过此步骤,可以生成一个新旧地址对照表 */
free = 0; // 统计空闲块的大小
busy = 0; // 统计占用块的大小,以确定各占用块的新地址
count = 0; // 统计紧缩后的内存块数量
j = 0;
for(i = 1; i <= len; i++) {
GetElem(space->recordList, i, (ElemType*) &r);
// 忽略空闲块,但是要统计它的大小
if(r->tag == 0) {
free += r->size;
continue;
}
// 先统计占用块数量
count++;
// 收集占用块,更新其地址信息
r->oldAddr = r->addr;
r->addr = busy;
busy += r->size;
// 把新的占用块信息排列到链表前面
space->recordList.elem[j++] = r;
}
// 记录空闲块的信息
if(free != 0) {
// 必须重新分配内存
r = (Record*) malloc(sizeof(Record));
r->addr = busy;
r->size = free;
r->tag = 0;
r->oldAddr = -1;
space->recordList.elem[j] = r;
count++;
// 指向最新的空闲块的记录
space->cur = count;
}
// 内存使用记录的新长度
space->recordList.length = count;
// printf("日志1. 计算新旧地址对照表(至此,只完成了\"占用\"内存的前移,以及\"空闲\"内存的合并):\n");
// PrintMemoryLayout(*space);
/* 2. 检查用户变量表,更新虚拟地址,以便紧缩之后的变量可以正常工作 */
// 用户变量表长度
len = ListLength(space->ptrList);
for(i = 0; i < len; i++) {
oldPtr = space->ptrList.elem[i];
// 获取旧地址在内存记录表中的索引
k = search(*space, *oldPtr);
// 更新为新地址
*oldPtr = ((Record*) (space->recordList.elem[k]))->addr;
}
// printf("日志2. 用户变量表更新完成(至此,用户变量(虚拟地址指针)已经指向了正确的内存,但是数据显示还不正常:\n");
// PrintMemoryLayout(*space);
/* 3. 检查链接,更新前驱和后继 */
len = ListLength(space->recordList);
for(i = 1; i <= len; i++) {
GetElem(space->recordList, i, (ElemType*) &r);
// 遇到空闲块时,可以结束循环了
if(r->tag==0) {
break;
}
if(r->pre != -1) {
k = search(*space, r->pre);
r->pre = ((Record*) (space->recordList.elem[k]))->addr;
}
if(r->next != -1) {
k = search(*space, r->next);
r->next = ((Record*) (space->recordList.elem[k]))->addr;
}
}
// printf("日志3. 内存记录中的前驱和后继已经可以正常显示:\n");
// PrintMemoryLayout(*space);
/* 4. 迁移用户数据 */
for(i = 1; i <= len; i++) {
GetElem(space->recordList, i, (ElemType*) &r);
// 紧缩后的空闲记录一定是排在最后的,所以遇到空闲记录时,就可以结束循环了
if(r->tag == 0) {
// 将该地址对应的内存处的数据抹掉(这是个可选操作)
memset(getPtr(*space, &(r->addr)), 0, r->size * BYTE);
break;
}
// 如果新旧地址一样,则无需迁移数据
if(r->addr == r->oldAddr) {
continue;
}
memmove(space->base + r->addr * BYTE, space->base + r->oldAddr * BYTE, r->size);
}
// printf("日志4. 数据迁移完成(至此,可以正常显示所有参数和用户数据:\n");
// PrintMemoryLayout(*space);
}
// 使用自定义的测试数据,这里还可以测试用户变量的变更情况
void TestCustom(Space* space, int* p[]) {
printf("+ 为 p1 申请10个内存并向此内存中写入数据 1 ...\n");
p[0] = AllocMemory(space, 10);
memset(getPtr(*space, p[0]), 1, 1);
PrintMemoryLayout(*space);
printf("+ 为 p2 申请50个内存并向此内存中写入数据 2 ...\n");
p[1] = AllocMemory(space, 50);
memset(getPtr(*space, p[1]), 2, 1);
PrintMemoryLayout(*space);
printf("+ 为 p3 申请20个内存并向此内存中写入数据 3 ...\n");
p[2] = AllocMemory(space, 20);
memset(getPtr(*space, p[2]), 3, 1);
PrintMemoryLayout(*space);
printf("- 释放 p2 处的50个内存...\n");
FreeMemory(space, p[1]);
PrintMemoryLayout(*space);
printf("+ 为 p4 申请5个内存并向此内存中写入数据 4 ...\n");
p[3] = AllocMemory(space, 5);
memset(getPtr(*space, p[3]), 4, 1);
PrintMemoryLayout(*space);
printf("+ 为 p5 申请10个内存并向此内存中写入数据 5 ...\n");
p[4] = AllocMemory(space, 10);
memset(getPtr(*space, p[4]), 5, 1);
PrintMemoryLayout(*space);
printf("- 释放 p4 处的5个内存...\n");
FreeMemory(space, p[3]);
PrintMemoryLayout(*space);
printf("- 释放 p3 处的20个内存...\n");
FreeMemory(space, p[2]);
PrintMemoryLayout(*space);
printf("+ 为 p6 申请15个内存并向此内存中写入数据 6 ...\n");
p[5] = AllocMemory(space, 15);
memset(getPtr(*space, p[5]), 6, 1);
((Record*)(space->recordList.elem[2]))->pre = 25;
((Record*)(space->recordList.elem[2]))->next = 0;
PrintMemoryLayout(*space);
printf("+ 为 p7 申请10个内存并向此内存中写入数据 7 ...\n");
p[6] = AllocMemory(space, 10);
memset(getPtr(*space, p[6]), 7, 1);
((Record*)(space->recordList.elem[4]))->pre = 60;
((Record*)(space->recordList.elem[4]))->next = -1;
PrintMemoryLayout(*space);
printf("+ 为 p8 申请15个内存并向此内存中写入数据 8 ...\n");
p[7] = AllocMemory(space, 15);
memset(getPtr(*space, p[7]), 8, 1);
((Record*)(space->recordList.elem[7]))->pre = -1;
((Record*)(space->recordList.elem[7]))->next = 15;
PrintMemoryLayout(*space);
printf("- 释放 p5 处的10个内存...\n");
FreeMemory(space, p[4]);
PrintMemoryLayout(*space);
printf("+ 为 p9 申请5个内存并向此内存中写入数据 9 ...\n");
p[8] = AllocMemory(space, 5);
memset(getPtr(*space, p[8]), 9, 1);
((Record*)(space->recordList.elem[3]))->pre = 15;
((Record*)(space->recordList.elem[3]))->next = 60;
PrintMemoryLayout(*space);
printf("- 释放 p1 处的10个内存...\n");
FreeMemory(space, p[0]);
PrintMemoryLayout(*space);
printf("+ 为 p10 申请5个内存并向此内存中写入数据 10 ...\n");
p[9] = AllocMemory(space, 5);
memset(getPtr(*space, p[9]), 10, 1);
((Record*)(space->recordList.elem[0]))->pre = -1;
((Record*)(space->recordList.elem[0]))->next = -1;
PrintMemoryLayout(*space);
}
// 对习题8.10给出的数据进行测试
void Test_8_10(Space* space, char* path) {
FILE* fp;
char line[100]; // 缓存读取到的行
char* p; // 指向分解出来的数字
Record* record;
// 每行读取到的数据
int addr;
int size;
int tag;
int pre;
int next;
// 记录虚拟地址
int* free[100];
int* ptr;
int count;
int i;
char data; // 用于验证数据迁移的测试数据
fp = fopen(path, "r");
// 没有有效内容,则直接返回空的顺序表
if(fp == NULL) {
return;
}
// 舍弃属于表头的前两行
fgets(line, 100, fp);
fgets(line, 100, fp);
count = 0;
i = 0;
data = 0;
// 如果没有到达文件尾,则一直读取
while(fgets(line, 100, fp)!=NULL) {
// 首地址
p = strtok(line, " |\n\r");
addr = atoi(p);
// 块大小
p = strtok(NULL, " |\n\r");
size = atoi(p);
// 标志域
p = strtok(NULL, " |\n\r");
tag = atoi(p);
// 前驱地址
p = strtok(NULL, " |\n\r");
pre = atoi(p);
// 后继地址
p = strtok(NULL, " |\n\r");
next = atoi(p);
// 申请size个内存
ptr = AllocMemory(space, size);
// 获取刚刚创建的内存记录的指针,完成前驱和后继的初始化
record = space->recordList.elem[i++];
record->pre = pre;
record->next = next;
/*
* 为刚刚申请的内存赋值,这是为了测试后续的数据迁移是否成功。
* 注意这里赋值时要将虚拟地址转换为真实地址。
*/
memset(getPtr(*space, ptr), ++data, 1);
if(tag == 0) {
// 该地址处的内存后续需要释放
free[count++] = ptr;
}
}
fclose(fp);
// 释放内存
for(i = 0; i < count; i++) {
FreeMemory(space, free[i]);
}
}
// 依照内存记录的首地址对内存记录进行排序时用到的比较器,该方法仅限内部使用
static int qsortCompar(const void* r1, const void* r2) {
Record* record1 = *((Record**) r1);
Record* record2 = *((Record**) r2);
return record1->addr - record2->addr;
}
/*
* 查找一条内存记录的索引该内存记录拥有旧地址oldAddr
*
* 注:这个查找其实很低效,因为:
*   首先,这个查找本身是顺序查找,效率不高;
*   其次,这个查找会被大量重复调用,更导致总效率低效。
*   更好的算法是使用后续学到的哈希表,将新旧地址存储到哈希表中会极大地加快查找速度。
*
* 该方法仅限内部使用
*/
static int search(Space space, int oldAddr) {
int k;
for(k = 0; k < space.recordList.length; k++) {
// 找到了匹配的旧地址值
if(oldAddr == ((Record*) (space.recordList.elem[k]))->oldAddr) {
return k;
}
}
return -1;
}

View File

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

View File

@@ -0,0 +1,239 @@
/*=======================
* 存储管理适用于习题8.16
========================*/
#include "MemoryManager.h"
// 初始化一片可用的存储
void InitSpace(Space* space) {
void* base; // 内存起始地址
Record* record; // 第一条记录
// 初始分配MAXSIZE个字节的内存且清零
base = calloc(MAXSIZE, BYTE);
space->base = base;
// 初始化内存使用记录
InitList(&(space->recordList));
record = (Record*) malloc(sizeof(Record));
record->addr = 0;
record->size = MAXSIZE;
record->tag = 0;
record->pre = -1;
record->next = -1;
record->oldAddr = -1;
// 插入第一条内存使用记录(空闲)
ListInsert(&(space->recordList), 1, record);
// 第一个可用的空闲块
space->cur = 1;
// 初始化地址链表
InitList(&(space->ptrList));
}
// 分配大小为n的内存并返回指向该内存的虚拟地址
int* AllocMemory(Space* space, int n) {
int found; // 标记是否找到可用的空闲块
int i, cur, len;
Record* record;
Record* newRecord;
int* ptr;
// 记下当前的空闲块记录位置
i = cur = space->cur;
found = 0;
// 获取当前内存记录表的长度
len = ListLength(space->recordList);
// 查找可用的空闲内存块
while(1) {
// 获取当前块的内存使用信息
GetElem(space->recordList, i, (ElemType*) &record);
// 跳过占用块,已经跳过容量不足的空闲块
if(record->tag == 1 || record->size < n) {
// 前进到下一条记录,注意是循环的
i = (i + 1) % len == 0 ? len : (i + 1) % len;
if(i == cur) {
break; // 如果又回到了本次搜索的起点,说明没有找到合适的空闲块
}
continue;
}
// 找到合适的空闲块
found = 1;
break;
}
// 没找到合适的空闲块
if(found == 0) {
return NULL;
}
// 在容差范围内,则整块分配
if(record->size - e <= n) {
record->tag = 1; // 转换为占用状态
// 否则,需要拆分空闲块
} else {
// 新建一条空闲记录
newRecord = (Record*) malloc(sizeof(Record));
newRecord->addr = record->addr + n;
newRecord->size = record->size - n;
newRecord->tag = 0;
newRecord->pre = -1;
newRecord->next = -1;
newRecord->oldAddr = -1;
// 插入新的空闲记录
ListInsert(&(space->recordList), len + 1, newRecord);
len += 1; // 链表长度增一
// 当前的空闲记录转为使用记录
record->size = n;
record->tag = 1;
}
// 构造一个虚拟地址
ptr = (int*) malloc(sizeof(int));
ListInsert(&(space->ptrList), ListLength(space->ptrList)+1, ptr);
*ptr = record->addr;
// 将游标cur指向下一条空闲记录
while(1) {
// 获取当前块的内存使用信息
GetElem(space->recordList, i, (ElemType*) &record);
// 跳过占用块
if(record->tag == 1) {
// 前进到下一条记录,注意是循环的
i = (i + 1) % len == 0 ? len : (i + 1) % len;
if(i == cur) {
break; // 如果又回到了本次搜索的起点,说明没有找到合适的空闲块
}
continue;
}
// 存储下一个可用空闲块的位置(不管找没找到都存储)
space->cur = i;
break;
}
return ptr; // 返回可用内存的虚拟地址
}
// 释放虚拟地址p处的内存
void FreeMemory(Space* space, int* p) {
int i, len;
Record* record;
// 获取当前内存记录的长度
len = ListLength(space->recordList);
// 遍历所有内存记录,查找待释放内存所在的记录
for(i = 1; i <= len; i++) {
// 获取当前块的内存使用信息
GetElem(space->recordList, i, (ElemType*) &record);
// 跳过不匹配的记录
if(*p != record->addr) {
continue;
}
// 如果该记录本来就是空闲,则直接返回
if(record->tag == 0) {
return;
}
// 将使用记录更改为空闲
record->tag = 0;
// 将游标指向刚刚回收的内存
space->cur = i;
// 将该地址对应的内存处的数据抹掉(这是个可选操作)
memset(getPtr(*space, p), 0, record->size*BYTE);
// 查找该虚拟地址在地址表中的位置
for(i = 0; i < space->ptrList.length; i++) {
if(space->ptrList.elem[i] == p) {
break;
}
}
// 删除该虚拟地址(用链表后面的数据覆盖前面的数据)
memmove(&(space->ptrList.elem[i]), &(space->ptrList.elem[i + 1]), (space->ptrList.length - i - 1) * sizeof(void*));
space->ptrList.length -= 1;
return;
}
}
// 将虚拟地址addr转换为底层的真实地址
void* getPtr(Space space, const int* addr) {
if(addr == NULL) {
return NULL;
} else {
if(*addr < 0 || *addr > MAXSIZE) {
return NULL;
}
return space.base + (*addr * BYTE);
}
}
// 打印内存布局,显示格式为:首地址 块大小 标志域 | 前驱 后继 | 数据
void PrintMemoryLayout(Space space) {
Record* record;
int i, len;
char* data;
len = ListLength(space.recordList);
for(i = 0; i < len; i++) {
record = space.recordList.elem[i];
printf("%2d ", record->addr);
printf("%2d ", record->size);
printf("%1d ", record->tag);
// 接下来输出前驱和后继信息
printf(" | ");
if(record->tag == 0) {
printf("\n");
continue;
}
if(record->pre == -1) {
printf(" ");
} else {
printf("%2d ", record->pre);
}
if(record->next == -1) {
printf(" ");
} else {
printf("%2d ", record->next);
}
// 接下来输出该内存中的数据,这里只输出第一个字节中存储的数据,只是用于测试
printf("");
data = (char*)calloc(1, sizeof(char));
memmove(data, getPtr(space, &(record->addr)), 1);
printf("%d", *data);
printf("\n");
}
printf("\n");
}

View File

@@ -0,0 +1,63 @@
/*=======================
* 存储管理适用于习题8.16
========================*/
#ifndef MEMORYMANAGER_H
#define MEMORYMANAGER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SqList.h"
#define MAXSIZE 100 // 最大内存量
#define e 0 // 容差
#define BYTE sizeof(char) // 1个字节
// 内存记录类型定义
typedef struct {
int addr; // 新地址域,存储内存块的当前最新地址,初始值是空间首地址
int size; // 块大小
int tag; // 标志域,指示内存是正在使用(1)还是空闲状态(0)
/* 存储块之间的链接关系,这与用户在虚拟地址层看到的链接关系可能并不一致 */
int pre; // 前驱
int next; // 后继
int oldAddr; // 进行存储紧缩后的旧地址
} Record;
// 可用空间类型定义
typedef struct {
void* base; // 内存起始地址
SqList recordList; // 内存使用记录
int cur; // 记录空闲块的内存记录的位置初值为1
/*
* 记录虚拟地址。
* 这其实是在模拟操作系统的行为:
* 用户使用的地址跟硬件的实际地址可能并不一致,所以需要操作系统构造一个虚拟地址集。
*/
SqList ptrList;
} Space;
// 初始化一片可用的存储
void InitSpace(Space* space);
// 分配大小为n的内存并返回指向该内存的虚拟地址
int* AllocMemory(Space* space, int n);
// 释放虚拟地址p处的内存
void FreeMemory(Space* space, int* p);
// 将虚拟地址addr转换为底层的真实地址
void* getPtr(Space space, const int* addr);
// 打印内存布局,显示格式为:首地址 块大小 标志域 | 前驱 后继 | 数据
void PrintMemoryLayout(Space space);
#endif

View File

@@ -0,0 +1,173 @@
/*=============================
* 线性表的顺序存储结构(顺序表)
*
* 包含算法: 2.3、2.4、2.5、2.6
=============================*/
#include "SqList.h"
/*
* ████████ 算法2.3 ████████
*
* 初始化
*
* 初始化成功则返回OK否则返回ERROR。
*/
Status InitList(SqList* L) {
// 分配指定容量的内存如果分配失败则返回NULL
(*L).elem = (ElemType*) malloc(LIST_INIT_SIZE * sizeof(ElemType));
if((*L).elem == NULL) {
// 存储内存失败
exit(OVERFLOW);
}
(*L).length = 0; // 初始化顺序表长度为0
(*L).listsize = LIST_INIT_SIZE; // 顺序表初始内存分配量
return OK; // 初始化成功
}
/*
* 计数
*
* 返回顺序表包含的有效元素的数量。
*/
int ListLength(SqList L) {
return L.length;
}
/*
* 取值
*
* 获取顺序表中第i个元素将其存储到e中。
* 如果可以找到返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数但这不符合编码的通用约定。
* 通常i的含义应该指索引即从0开始计数。
*/
Status GetElem(SqList L, int i, ElemType* e) {
// 因为i的含义是位置所以其合法范围是[1, length]
if(i < 1 || i > L.length) {
return ERROR; //i值不合法
}
*e = L.elem[i - 1];
return OK;
}
/*
* ████████ 算法2.4 ████████
*
* 插入
*
* 向顺序表第i个位置上插入e插入成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListInsert(SqList* L, int i, ElemType e) {
ElemType* newbase;
ElemType* p, * q;
// 确保顺序表结构存在
if(L == NULL || (*L).elem == NULL) {
return ERROR;
}
// i值越界
if(i < 1 || i > (*L).length + 1) {
return ERROR;
}
// 若存储空间已满,则增加新空间
if((*L).length >= (*L).listsize) {
// 基于现有空间扩容
newbase = (ElemType*) realloc((*L).elem, ((*L).listsize + LISTINCREMENT) * sizeof(ElemType));
if(newbase == NULL) {
// 存储内存失败
exit(OVERFLOW);
}
// 新基址
(*L).elem = newbase;
// 存的存储空间
(*L).listsize += LISTINCREMENT;
}
// q为插入位置
q = &(*L).elem[i - 1];
// 1.右移元素,腾出位置
for(p = &(*L).elem[(*L).length - 1]; p >= q; --p) {
*(p + 1) = *p;
}
// 2.插入e
*q = e;
// 3.表长增1
(*L).length++;
return OK;
}
/*
* ████████ 算法2.5 ████████
*
* 删除
*
* 删除顺序表第i个位置上的元素并将被删除元素存储到e中。
* 删除成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListDelete(SqList* L, int i, ElemType* e) {
ElemType* p, * q;
// 确保顺序表结构存在
if(L == NULL || (*L).elem == NULL) {
return ERROR;
}
// i值越界
if(i < 1 || i > (*L).length) {
return ERROR;
}
// p为被删除元素的位置
p = &(*L).elem[i - 1];
// 1.获取被删除元素
*e = *p;
// 表尾元素位置
q = (*L).elem + (*L).length - 1;
// 2.左移元素,被删除元素的位置上会有新元素进来
for(++p; p <= q; ++p) {
*(p - 1) = *p;
}
// 3.表长减1
(*L).length--;
return OK;
}
/*
* 遍历
*
* 用visit函数访问顺序表L
*/
void ListTraverse(SqList L, void(Visit)(ElemType)) {
int i;
for(i = 0; i < L.length; i++) {
Visit(L.elem[i]);
}
printf("\n");
}

View File

@@ -0,0 +1,94 @@
/*=============================
* 线性表的顺序存储结构(顺序表)
*
* 包含算法: 2.3、2.4、2.5、2.6
=============================*/
#ifndef SQLIST_H
#define SQLIST_H
#include <stdio.h>
#include <stdlib.h> // 提供malloc、realloc、free、exit原型
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define LIST_INIT_SIZE 100 // 顺序表存储空间的初始分配量
#define LISTINCREMENT 10 // 顺序表存储空间的分配增量
/* 顺序表元素类型定义 */
typedef void* ElemType;
/*
* 顺序表结构
*
* 注elem在使用前需要先为其分配内存且元素从elem[0]处开始存储
*/
typedef struct {
ElemType* elem; // 顺序表存储空间的基址(指向顺序表所占内存的起始位置)
int length; // 当前顺序表长度(包含多少元素)
int listsize; // 当前分配的存储容量(可以存储多少元素)
} SqList;
/*
* ████████ 算法2.3 ████████
*
* 初始化
*
* 初始化成功则返回OK否则返回ERROR。
*/
Status InitList(SqList* L);
/*
* 计数
*
* 返回顺序表包含的有效元素的数量。
*/
int ListLength(SqList L);
/*
* 取值
*
* 获取顺序表中第i个元素将其存储到e中。
* 如果可以找到返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数但这不符合编码的通用约定。
* 通常i的含义应该指索引即从0开始计数。
*/
Status GetElem(SqList L, int i, ElemType* e);
/*
* ████████ 算法2.4 ████████
*
* 插入
*
* 向顺序表第i个位置上插入e插入成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListInsert(SqList* L, int i, ElemType e);
/*
* ████████ 算法2.5 ████████
*
* 删除
*
* 删除顺序表第i个位置上的元素并将被删除元素存储到e中。
* 删除成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListDelete(SqList* L, int i, ElemType* e);
/*
* 遍历
*
* 用visit函数访问顺序表L
*/
void ListTraverse(SqList L, void (Visit)(ElemType));
#endif

View File

@@ -0,0 +1,17 @@
| 首地址 | 块大小 | 标志域 | 前驱指针 | 后继指针 |
| :----: | :----: | :----: | :----: | :----: |
| 0 | 5 | 1 | -1 | 10 |
| 5 | 5 | 0 | -1 | -1 |
| 10 | 5 | 1 | 15 | 40 |
| 15 | 10 | 1 | -1 | -1 |
| 25 | 5 | 0 | -1 | -1 |
| 30 | 10 | 0 | -1 | -1 |
| 40 | 5 | 1 | 45 | 65 |
| 45 | 10 | 1 | -1 | -1 |
| 55 | 5 | 0 | -1 | -1 |
| 60 | 5 | 0 | -1 | -1 |
| 65 | 5 | 1 | 85 | -1 |
| 70 | 5 | 0 | -1 | -1 |
| 75 | 10 | 0 | -1 | -1 |
| 85 | 5 | 1 | 90 | -1 |
| 90 | 10 | 1 | -1 | -1 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -148,3 +148,9 @@ add_subdirectory(07.39)
add_subdirectory(07.40)
add_subdirectory(07.41)
add_subdirectory(07.42)
add_subdirectory(08.11-08.12)
add_subdirectory(08.13)
add_subdirectory(08.14)
add_subdirectory(08.15)
add_subdirectory(08.16)

View File

@@ -0,0 +1,127 @@
#include <stdio.h>
#include "Status.h"
#include "SqStack.h"
/* 宏定义 */
#define MAXSIZE 200 // 内存总大小
/* 空闲空间类型定义 */
typedef struct {
void* base; // 可用空间内存的起始地址
int size; // 空闲空间大小(按字节计数)
SqStack block; // 已经使用的内存块大小,先申请的内存信息先入栈
} Space;
// 初始化内存空间及内存信息栈
void InitSpace(Space* space);
// 打印内存布局,查看当前内存的使用情况
void PrintMemoryLayout(Space space);
// 申请大小为n的内存块返回申请到的内存起始地址
void* Algo_8_11(Space* space, int n);
// 回收内存,遵循"最后分配者最先释放"的原则
Status Algo_8_12(Space* space);
int main(int argc, char* argv[]) {
Space space;
int s[5] = {50, 50, 150, 100, 50}; // 定义内存申请数量
int i;
// 初始化内存
InitSpace(&space);
PrintMemoryLayout(space);
printf("\n");
// 申请/释放内存
for(i = 0; i < 5; i++) {
printf("申请 %d 个内存后...\n", s[i]);
Algo_8_11(&space, s[i]);
PrintMemoryLayout(space);
printf("\n");
}
return 0;
}
// 初始化一块空闲空间
void InitSpace(Space* space) {
// 申请MAX字节的空间(假设总能初始化成功)
space->base = malloc(MAXSIZE);
space->size = MAXSIZE;
// 初始化内存信息栈
InitStack(&(space->block));
}
// 打印内存布局,查看当前内存的使用情况
void PrintMemoryLayout(Space space) {
int i;
for(i = 1; i <= MAXSIZE; i++) {
if(i <= space.size) {
printf("");
} else {
printf("");
}
// 预定每隔20个换一下行
if(i % 20 == 0) {
printf("\n");
}
}
if(MAXSIZE % 20 != 0) {
printf("\n");
}
}
// 申请大小为n的内存块返回申请到的内存起始地址
void* Algo_8_11(Space* space, int n) {
/*
* 如果可用的内存数量少于申请的内存数量,
* 则需要释放最近申请使用的内存以腾出空间。
*/
while(space->size < n && Algo_8_12(space) == OK) {
// 直到空闲内存满足申请要求,或者全部占用内存已经释放,才会退出循环
}
// 如果没有足够的内存可用,则返回空指针
if(space->size < n) {
return NULL;
}
// 可用空间减小
space->size -= n;
// 记下成功申请到的内存
Push(&(space->block), n);
// 返回申请到的内存起始地址
return space->base + space->size;
}
// 回收内存,遵循"最后分配者最先释放"的原则
Status Algo_8_12(Space* space) {
int e;
// 已经没有可释放内存时返回ERROR
if(StackEmpty(space->block)) {
return ERROR;
}
// 将最后申请的内存释放掉
Pop(&(space->block), &e);
// 可用空间增大
space->size += e;
return OK;
}

View File

@@ -0,0 +1,82 @@
[Project]
FileName=08.11-08.12.dev
Name=08.11-08.12
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
[Unit3]
FileName=SqStack.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=SqStack.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit1]
FileName=08.11-08.12.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

View File

@@ -0,0 +1,91 @@
/*=========================
* 栈的顺序存储结构(顺序栈)
==========================*/
#include "SqStack.h" //**▲03 栈和队列**//
/*
* 初始化
*
* 构造一个空栈。初始化成功则返回OK否则返回ERROR。
*/
Status InitStack(SqStack* S) {
if(S == NULL) {
return ERROR;
}
(*S).base = (SElemType*) malloc(STACK_INIT_SIZE * sizeof(SElemType));
if((*S).base == NULL) {
exit(OVERFLOW);
}
(*S).top = (*S).base;
(*S).stacksize = STACK_INIT_SIZE;
return OK;
}
/*
* 判空
*
* 判断顺序栈中是否包含有效数据。
*
* 返回值:
* TRUE : 顺序栈为空
* FALSE: 顺序栈不为空
*/
Status StackEmpty(SqStack S) {
if(S.top == S.base) {
return TRUE;
} else {
return FALSE;
}
}
/*
* 入栈
*
* 将元素e压入到栈顶。
*/
Status Push(SqStack* S, SElemType e) {
if(S == NULL || (*S).base == NULL) {
return ERROR;
}
// 栈满时,追加存储空间
if((*S).top - (*S).base >= (*S).stacksize) {
(*S).base = (SElemType*) realloc((*S).base, ((*S).stacksize + STACKINCREMENT) * sizeof(SElemType));
if((*S).base == NULL) {
exit(OVERFLOW); // 存储分配失败
}
(*S).top = (*S).base + (*S).stacksize;
(*S).stacksize += STACKINCREMENT;
}
// 进栈先赋值,栈顶指针再自增
*(S->top++) = e;
return OK;
}
/*
* 出栈
*
* 将栈顶元素弹出并用e接收。
*/
Status Pop(SqStack* S, SElemType* e) {
if(S == NULL || (*S).base == NULL) {
return ERROR;
}
if((*S).top == (*S).base) {
return ERROR;
}
// 出栈栈顶指针先递减,再赋值
*e = *(--(*S).top);
return OK;
}

View File

@@ -0,0 +1,60 @@
/*=========================
* 栈的顺序存储结构(顺序栈)
==========================*/
#ifndef SQSTACK_H
#define SQSTACK_H
#include <stdio.h>
#include <stdlib.h> // 提供malloc、realloc、free、exit原型
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define STACK_INIT_SIZE 100 // 顺序栈存储空间的初始分配量
#define STACKINCREMENT 10 // 顺序栈存储空间的分配增量
/* 顺序栈元素类型定义 */
typedef int SElemType;
// 顺序栈元素结构
typedef struct {
SElemType* base; // 栈底指针
SElemType* top; // 栈顶指针
int stacksize; // 当前已分配的存储空间,以元素为单位
} SqStack;
/*
* 初始化
*
* 构造一个空栈。初始化成功则返回OK否则返回ERROR。
*/
Status InitStack(SqStack* S);
/*
* 判空
*
* 判断顺序栈中是否包含有效数据。
*
* 返回值:
* TRUE : 顺序栈为空
* FALSE: 顺序栈不为空
*/
Status StackEmpty(SqStack S);
/*
* 入栈
*
* 将元素e压入到栈顶。
*/
Status Push(SqStack* S, SElemType e);
/*
* 出栈
*
* 将栈顶元素弹出并用e接收。
*/
Status Pop(SqStack* S, SElemType* e);
#endif

View File

@@ -0,0 +1,66 @@
#include <stdio.h>
#include "BoundaryTagMethod.h" //**▲08 动态存储管理**//
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void Algo_8_13(Space* pav, Space p);
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);
Algo_8_13(&pav, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}
/*
* 边界标识法的内存回收算法
*
* 对指针p处的内存进行释放
*/
void Algo_8_13(Space* pav, Space p) {
FreeBoundTag(pav, p);
}

View File

@@ -0,0 +1,82 @@
[Project]
FileName=08.13.dev
Name=08.13
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=08.13.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=BoundaryTagMethod.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=BoundaryTagMethod.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

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,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,67 @@
#include <stdio.h>
#include "BuddySystem.h" //**▲08 动态存储管理**//
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void Algo_8_14(FreeList avail, WORD* p);
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++) {
Algo_8_14(avail, p[a[i]]);
PrintMemoryLayout();
printf("\n");
}
}
PressEnterToContinue(debug);
return 0;
}
/*
* 伙伴系统的内存回收算法
*
* 对指针p处的内存进行释放(这类似于free()只是对内存进行释放操作至于置空指针p的操作应交给调用方完成)
*
* 注这里没有验证p的取值调用方应当确保p在合规的范围
*/
void Algo_8_14(FreeList avail, WORD* p) {
FreeBuddy(avail, p);
}

View File

@@ -0,0 +1,82 @@
[Project]
FileName=08.14.dev
Name=08.14
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=08.14.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=BuddySystem.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=BuddySystem.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

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,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,86 @@
#include <stdio.h>
#include "MemoryManager.h"
/*
* 内存整理
*
* 将空闲块收集起来,链接成一条空闲块链表。
*/
void Algo_8_15(Space* space);
int main(int argc, char** argv) {
Space space;
int i;
int a[11] = {10, 20, 30, 15, 20, 5, -1, -3, 0, 15, 10};
WORD* p[11];
InitSpace(&space);
printf("初始内存布局:\n");
PrintMemoryLayout(space);
/*
* 注:
* 申请a[4]时仅剩25个"字"当前容差是5因此本次会直接分配20+5=25个"字"
* 申请a[5]时,已经没有可用的字,所以申请失败
* 申请a[10]时,处在容差范围内,会整块分配
*/
for(i = 0; i < 11; i++) {
if(a[i] > 0) {
printf("+ 申请 %d 个字的存储\n", a[i]);
p[i] = AllocMemory(&space, a[i]);
} else {
printf("- 释放 %d 个字的存储\n", a[-a[i]]);
FreeMemory(&space, p[-a[i]]);
}
PrintMemoryLayout(space);
if(i == 8) {
printf("内存整理...\n");
Algo_8_15(&space);
PrintMemoryLayout(space);
}
}
}
/*
* 内存整理
*
* 将空闲块收集起来,链接成一条空闲块链表。
*/
void Algo_8_15(Space* space) {
WORD* p, * q;
space->avail = NULL;
q = NULL;
// 遍历整个堆
for(p = space->lowbound; p < space->highbound; p += p->cellsize) {
if(p->tag == 1) {
continue;
}
// 先把后继置空
p->next = NULL;
// 遇到了第一个空闲块
if(space->avail == NULL) {
space->avail = p;
q = p;
continue;
}
// 如果两个空闲块地址不相邻,则进行链接
if(q + q->cellsize != p) {
q->next = p;
// 对于地址相邻的空闲块,要合并
} else {
q->cellsize += p->cellsize;
}
}
}

View File

@@ -0,0 +1,82 @@
[Project]
FileName=08.15.dev
Name=08.15
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=08.15.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=MemoryManager.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=MemoryManager.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=

View File

@@ -0,0 +1,132 @@
/*=======================
* 存储管理适用于习题8.15
========================*/
#include "MemoryManager.h"
// 初始化一片可用的存储
void InitSpace(Space* space) {
space->avail = (WORD*) malloc(MAXSIZE * sizeof(WORD));
space->avail->tag = 0;
space->avail->cellsize = MAXSIZE;
space->avail->next = NULL;
space->lowbound = space->avail;
space->highbound = space->avail + MAXSIZE;
}
// 分配大小为n的内存并返回指向该内存的虚拟地址
WORD* AllocMemory(Space* space, int n) {
WORD* p, * q;
if(space->avail == NULL) {
return NULL;
}
p = space->avail;
q = NULL;
// 如果第一个空闲块不满足条件
if(p->cellsize < n) {
// 查找满足条件的空闲内存
while(p != NULL && p->cellsize < n) {
q = p;
p = p->next;
}
}
// 没有符合条件的空闲内存
if(p == NULL) {
return NULL;
}
// 在容差范围内,则整块分配
if(p->cellsize - e <= n) {
if(q == NULL) {
space->avail = p->next;
} else {
q->next = p->next;
}
// 分割空闲块
} else {
if(q == NULL) {
space->avail += n;
space->avail->tag = 0;
space->avail->cellsize = p->cellsize - n;
space->avail->next = p->next;
} else {
q->next = p + n;
q->next->tag = 0;
q->next->cellsize = p->cellsize - n;
}
p->cellsize = n;
}
// 标记为占用
p->tag = 1;
return p;
}
// 释放指针p处的内存
void FreeMemory(Space* space, WORD* p) {
p->tag = 0; // 简单地标记为空闲,以待后续整理
}
// 打印内存布局
void PrintMemoryLayout(Space space) {
int i, count;
WORD* p;
// 遍历所有块
for(p = space.lowbound; p < space.highbound; p += p->cellsize) {
if(p->tag == 0) {
p->flag = -1; // 暂时标记为"不可用"的空闲块
} else {
p->flag = 1; // 标记为占用
}
}
// 遍历空闲块链表
for(p = space.avail; p != NULL; p = p->next) {
p->flag = 0; // 更新为"可用"的空闲块
}
count = 0;
for(p = space.lowbound; p < space.highbound; p += p->cellsize) {
for(i = 0; i < p->cellsize; i++) {
switch(p->flag) {
case -1:
printf("");
break;
case 0:
printf("");
break;
case 1:
printf("");
break;
default:
printf("错误的标记!\n");
return;
}
count++;
// 每隔20个字换一下行
if(count % 20 == 0) {
printf("\n");
}
}
}
if(count % 20 != 0) {
printf("\n");
}
printf("\n");
}

View File

@@ -0,0 +1,58 @@
/*=======================
* 存储管理适用于习题8.15
========================*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifndef MEMORYMANAGER_H
#define MEMORYMANAGER_H
/* 宏定义 */
#define MAXSIZE 100 // 内存总容量
#define e 5 // 分配空间时用到的容差
/*
* 内存"字"的类型定义
*
* 所谓"字"是指空间分配的最小单位,它不是字节。
* 一个"字"有多大,取决于对"字"结构是如何定义的。
*/
typedef struct WORD {
int tag; // 块标志0空闲1占用
int cellsize; // 当前块的大小
struct WORD* next; // 指向下一个空闲块
/*
* 使用标记0代表空闲1代表占用-1代表虽然空闲但是不在空闲链表中。
* 这个标记仅用来测试打印功能,并没有其它实质性的用处。
*/
int flag;
} WORD;
// "堆"空间
typedef struct {
struct WORD* lowbound; // 空间下界
struct WORD* highbound; // 空间上界
struct WORD* avail; // 指向可用空间链表
} Space;
// 初始化一片可用的存储
void InitSpace(Space* space);
// 分配大小为n的内存并返回指向该内存的虚拟地址
WORD* AllocMemory(Space* space, int n);
// 释放指针p处的内存
void FreeMemory(Space* space, WORD* p);
// 打印内存布局
void PrintMemoryLayout(Space space);
#endif

View File

@@ -0,0 +1,443 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "MemoryManager.h"
/*
* 存储紧缩算法
*
* 该算法的实现过程与存储管理方案息息相关。
* 此处的紧缩算法是建立在自己实现的MemoryManager之上的。
* 如果有别的存储管理方案,那么紧缩算法也需要随之改变。
*
* 具体步骤的描述参见教材。
*/
void Algo_8_16(Space* space);
// 依照内存记录的首地址对内存记录进行排序时用到的比较器,该方法仅限内部使用
static int qsortCompar(const void* r1, const void* r2);
// 查找一条内存记录的索引该内存记录拥有旧地址oldAddr该方法仅限内部使用
static int search(Space space, int oldAddr);
// 对习题8.10给出的数据进行测试
void Test_8_10(Space* space, char* path);
// 使用自定义的测试数据,这里还可以测试用户变量的变更情况
void TestCustom(Space* space, int* p[]);
int main(int argc, char** argv) {
// printf("=== 使用习题8.10中的测试数据 ===\n\n");
// {
// Space space;
//
// // 初始化一片可用的存储
// InitSpace(&space);
// printf("初始内存布局:\n");
// PrintMemoryLayout(space);
//
// // 测试习题8.10的数据
// Test_8_10(&space, "TestData_8_10.md");
// printf("习题8.10中的内存布局:\n");
// PrintMemoryLayout(space);
//
// // 存储紧缩
// Algo_8_16(&space);
// printf("进行存储紧缩后的内存布局:\n");
// PrintMemoryLayout(space);
// }
printf("=== 使用自定义的测试数据 ===\n\n");
{
Space space;
int* p[10]; // 用户变量表(虚拟地址)
char* data = (char*)malloc(sizeof(char));
// 初始化一片可用的存储
InitSpace(&space);
printf("初始内存布局:\n");
PrintMemoryLayout(space);
// 自定义数据
TestCustom(&space, p);
// 存储紧缩
Algo_8_16(&space);
printf("进行存储紧缩后的内存布局:\n");
PrintMemoryLayout(space);
printf("对照内存布局,验证用户变量是否已经同步更新:\n");
memmove(data, getPtr(space, p[9]), 1);
printf("p10 = %d\n", *data);
memmove(data, getPtr(space, p[6]), 1);
printf("p7 = %d\n", *data);
memmove(data, getPtr(space, p[7]), 1);
printf("p8 = %d\n", *data);
memmove(data, getPtr(space, p[5]), 1);
printf("p6 = %d\n", *data);
memmove(data, getPtr(space, p[8]), 1);
printf("p9 = %d\n", *data);
}
}
/*
* 存储紧缩算法
*
* 该算法的实现过程与存储管理方案息息相关。
* 此处的紧缩算法是建立在自己实现的MemoryManager之上的。
* 如果有别的存储管理方案,那么紧缩算法也需要随之改变。
*
* 具体步骤的描述参见教材。
*/
void Algo_8_16(Space* space) {
Record* r;
int len;
int i, j, k, count;
int busy, free;
int* oldPtr; // 记录旧的虚拟地址
// 当前包含的内存使用记录条数
len = ListLength(space->recordList);
/* 0. 对内存记录表进行排序,这有利于后续的内存数据迁移 */
qsort(space->recordList.elem, len, sizeof(ElemType), qsortCompar);
// printf("日志0. 对内存记录进行排序(至此,只完成了对首地址的顺序):\n");
// PrintMemoryLayout(*space);
/* 1. 确定经过存储紧缩后,各内存块的新地址,换句话说,经过此步骤,可以生成一个新旧地址对照表 */
free = 0; // 统计空闲块的大小
busy = 0; // 统计占用块的大小,以确定各占用块的新地址
count = 0; // 统计紧缩后的内存块数量
j = 0;
for(i = 1; i <= len; i++) {
GetElem(space->recordList, i, (ElemType*) &r);
// 忽略空闲块,但是要统计它的大小
if(r->tag == 0) {
free += r->size;
continue;
}
// 先统计占用块数量
count++;
// 收集占用块,更新其地址信息
r->oldAddr = r->addr;
r->addr = busy;
busy += r->size;
// 把新的占用块信息排列到链表前面
space->recordList.elem[j++] = r;
}
// 记录空闲块的信息
if(free != 0) {
// 必须重新分配内存
r = (Record*) malloc(sizeof(Record));
r->addr = busy;
r->size = free;
r->tag = 0;
r->oldAddr = -1;
space->recordList.elem[j] = r;
count++;
// 指向最新的空闲块的记录
space->cur = count;
}
// 内存使用记录的新长度
space->recordList.length = count;
// printf("日志1. 计算新旧地址对照表(至此,只完成了\"占用\"内存的前移,以及\"空闲\"内存的合并):\n");
// PrintMemoryLayout(*space);
/* 2. 检查用户变量表,更新虚拟地址,以便紧缩之后的变量可以正常工作 */
// 用户变量表长度
len = ListLength(space->ptrList);
for(i = 0; i < len; i++) {
// 注意C++中的强制类型转换方式
oldPtr = static_cast<int*>(space->ptrList.elem[i]);
// 获取旧地址在内存记录表中的索引
k = search(*space, *oldPtr);
// 更新为新地址
*oldPtr = ((Record*) (space->recordList.elem[k]))->addr;
}
// printf("日志2. 用户变量表更新完成(至此,用户变量(虚拟地址指针)已经指向了正确的内存,但是数据显示还不正常:\n");
// PrintMemoryLayout(*space);
/* 3. 检查链接,更新前驱和后继 */
len = ListLength(space->recordList);
for(i = 1; i <= len; i++) {
GetElem(space->recordList, i, (ElemType*) &r);
// 遇到空闲块时,可以结束循环了
if(r->tag==0) {
break;
}
if(r->pre != -1) {
k = search(*space, r->pre);
r->pre = ((Record*) (space->recordList.elem[k]))->addr;
}
if(r->next != -1) {
k = search(*space, r->next);
r->next = ((Record*) (space->recordList.elem[k]))->addr;
}
}
// printf("日志3. 内存记录中的前驱和后继已经可以正常显示:\n");
// PrintMemoryLayout(*space);
/* 4. 迁移用户数据 */
for(i = 1; i <= len; i++) {
GetElem(space->recordList, i, (ElemType*) &r);
// 紧缩后的空闲记录一定是排在最后的,所以遇到空闲记录时,就可以结束循环了
if(r->tag == 0) {
// 将该地址对应的内存处的数据抹掉(这是个可选操作)
memset(getPtr(*space, &(r->addr)), 0, r->size * BYTE);
break;
}
// 如果新旧地址一样,则无需迁移数据
if(r->addr == r->oldAddr) {
continue;
}
memmove(space->base + r->addr * BYTE, space->base + r->oldAddr * BYTE, r->size);
}
// printf("日志4. 数据迁移完成(至此,可以正常显示所有参数和用户数据:\n");
// PrintMemoryLayout(*space);
}
// 使用自定义的测试数据,这里还可以测试用户变量的变更情况
void TestCustom(Space* space, int* p[]) {
printf("+ 为 p1 申请10个内存并向此内存中写入数据 1 ...\n");
p[0] = AllocMemory(space, 10);
memset(getPtr(*space, p[0]), 1, 1);
PrintMemoryLayout(*space);
printf("+ 为 p2 申请50个内存并向此内存中写入数据 2 ...\n");
p[1] = AllocMemory(space, 50);
memset(getPtr(*space, p[1]), 2, 1);
PrintMemoryLayout(*space);
printf("+ 为 p3 申请20个内存并向此内存中写入数据 3 ...\n");
p[2] = AllocMemory(space, 20);
memset(getPtr(*space, p[2]), 3, 1);
PrintMemoryLayout(*space);
printf("- 释放 p2 处的50个内存...\n");
FreeMemory(space, p[1]);
PrintMemoryLayout(*space);
printf("+ 为 p4 申请5个内存并向此内存中写入数据 4 ...\n");
p[3] = AllocMemory(space, 5);
memset(getPtr(*space, p[3]), 4, 1);
PrintMemoryLayout(*space);
printf("+ 为 p5 申请10个内存并向此内存中写入数据 5 ...\n");
p[4] = AllocMemory(space, 10);
memset(getPtr(*space, p[4]), 5, 1);
PrintMemoryLayout(*space);
printf("- 释放 p4 处的5个内存...\n");
FreeMemory(space, p[3]);
PrintMemoryLayout(*space);
printf("- 释放 p3 处的20个内存...\n");
FreeMemory(space, p[2]);
PrintMemoryLayout(*space);
printf("+ 为 p6 申请15个内存并向此内存中写入数据 6 ...\n");
p[5] = AllocMemory(space, 15);
memset(getPtr(*space, p[5]), 6, 1);
((Record*)(space->recordList.elem[2]))->pre = 25;
((Record*)(space->recordList.elem[2]))->next = 0;
PrintMemoryLayout(*space);
printf("+ 为 p7 申请10个内存并向此内存中写入数据 7 ...\n");
p[6] = AllocMemory(space, 10);
memset(getPtr(*space, p[6]), 7, 1);
((Record*)(space->recordList.elem[4]))->pre = 60;
((Record*)(space->recordList.elem[4]))->next = -1;
PrintMemoryLayout(*space);
printf("+ 为 p8 申请15个内存并向此内存中写入数据 8 ...\n");
p[7] = AllocMemory(space, 15);
memset(getPtr(*space, p[7]), 8, 1);
((Record*)(space->recordList.elem[7]))->pre = -1;
((Record*)(space->recordList.elem[7]))->next = 15;
PrintMemoryLayout(*space);
printf("- 释放 p5 处的10个内存...\n");
FreeMemory(space, p[4]);
PrintMemoryLayout(*space);
printf("+ 为 p9 申请5个内存并向此内存中写入数据 9 ...\n");
p[8] = AllocMemory(space, 5);
memset(getPtr(*space, p[8]), 9, 1);
((Record*)(space->recordList.elem[3]))->pre = 15;
((Record*)(space->recordList.elem[3]))->next = 60;
PrintMemoryLayout(*space);
printf("- 释放 p1 处的10个内存...\n");
FreeMemory(space, p[0]);
PrintMemoryLayout(*space);
printf("+ 为 p10 申请5个内存并向此内存中写入数据 10 ...\n");
p[9] = AllocMemory(space, 5);
memset(getPtr(*space, p[9]), 10, 1);
((Record*)(space->recordList.elem[0]))->pre = -1;
((Record*)(space->recordList.elem[0]))->next = -1;
PrintMemoryLayout(*space);
}
// 对习题8.10给出的数据进行测试
void Test_8_10(Space* space, char* path) {
FILE* fp;
char line[100]; // 缓存读取到的行
char* p; // 指向分解出来的数字
Record* record;
// 每行读取到的数据
int addr;
int size;
int tag;
int pre;
int next;
// 记录虚拟地址
int* free[100];
int* ptr;
int count;
int i;
char data; // 用于验证数据迁移的测试数据
fp = fopen(path, "r");
// 没有有效内容,则直接返回空的顺序表
if(fp == NULL) {
return;
}
// 舍弃属于表头的前两行
fgets(line, 100, fp);
fgets(line, 100, fp);
count = 0;
i = 0;
data = 0;
// 如果没有到达文件尾,则一直读取
while(fgets(line, 100, fp)!=NULL) {
// 首地址
p = strtok(line, " |\n\r");
addr = atoi(p);
// 块大小
p = strtok(NULL, " |\n\r");
size = atoi(p);
// 标志域
p = strtok(NULL, " |\n\r");
tag = atoi(p);
// 前驱地址
p = strtok(NULL, " |\n\r");
pre = atoi(p);
// 后继地址
p = strtok(NULL, " |\n\r");
next = atoi(p);
// 申请size个内存
ptr = AllocMemory(space, size);
// 获取刚刚创建的内存记录的指针,完成前驱和后继的初始化
// 注意C++中的强制类型转换方式
record = static_cast<Record*>(space->recordList.elem[i++]);
record->pre = pre;
record->next = next;
/*
* 为刚刚申请的内存赋值,这是为了测试后续的数据迁移是否成功。
* 注意这里赋值时要将虚拟地址转换为真实地址。
*/
memset(getPtr(*space, ptr), ++data, 1);
if(tag == 0) {
// 该地址处的内存后续需要释放
free[count++] = ptr;
}
}
fclose(fp);
// 释放内存
for(i = 0; i < count; i++) {
FreeMemory(space, free[i]);
}
}
// 依照内存记录的首地址对内存记录进行排序时用到的比较器,该方法仅限内部使用
static int qsortCompar(const void* r1, const void* r2) {
Record* record1 = *((Record**) r1);
Record* record2 = *((Record**) r2);
return record1->addr - record2->addr;
}
/*
* 查找一条内存记录的索引该内存记录拥有旧地址oldAddr
*
* 注:这个查找其实很低效,因为:
*   首先,这个查找本身是顺序查找,效率不高;
*   其次,这个查找会被大量重复调用,更导致总效率低效。
*   更好的算法是使用后续学到的哈希表,将新旧地址存储到哈希表中会极大地加快查找速度。
*
* 该方法仅限内部使用
*/
static int search(Space space, int oldAddr) {
int k;
for(k = 0; k < space.recordList.length; k++) {
// 找到了匹配的旧地址值
if(oldAddr == ((Record*) (space.recordList.elem[k]))->oldAddr) {
return k;
}
}
return -1;
}

View File

@@ -0,0 +1,111 @@
[Project]
FileName=08.16.dev
Name=08.16
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=6
[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=08.16.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit2]
FileName=MemoryManager.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit3]
FileName=MemoryManager.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit4]
FileName=SqList.cpp
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit5]
FileName=SqList.h
CompileCpp=0
Folder=
Compile=1
Link=1
Priority=1000
OverrideBuildCmd=0
BuildCmd=
[Unit6]
FileName=TestData_8_10.md
Folder=
Compile=0
Link=0
Priority=1000
OverrideBuildCmd=0
BuildCmd=

View File

@@ -0,0 +1,240 @@
/*=======================
* 存储管理适用于习题8.16
========================*/
#include "MemoryManager.h"
// 初始化一片可用的存储
void InitSpace(Space* space) {
void* base; // 内存起始地址
Record* record; // 第一条记录
// 初始分配MAXSIZE个字节的内存且清零
base = calloc(MAXSIZE, BYTE);
space->base = base;
// 初始化内存使用记录
InitList(&(space->recordList));
record = (Record*) malloc(sizeof(Record));
record->addr = 0;
record->size = MAXSIZE;
record->tag = 0;
record->pre = -1;
record->next = -1;
record->oldAddr = -1;
// 插入第一条内存使用记录(空闲)
ListInsert(&(space->recordList), 1, record);
// 第一个可用的空闲块
space->cur = 1;
// 初始化地址链表
InitList(&(space->ptrList));
}
// 分配大小为n的内存并返回指向该内存的虚拟地址
int* AllocMemory(Space* space, int n) {
int found; // 标记是否找到可用的空闲块
int i, cur, len;
Record* record;
Record* newRecord;
int* ptr;
// 记下当前的空闲块记录位置
i = cur = space->cur;
found = 0;
// 获取当前内存记录表的长度
len = ListLength(space->recordList);
// 查找可用的空闲内存块
while(1) {
// 获取当前块的内存使用信息
GetElem(space->recordList, i, (ElemType*) &record);
// 跳过占用块,已经跳过容量不足的空闲块
if(record->tag == 1 || record->size < n) {
// 前进到下一条记录,注意是循环的
i = (i + 1) % len == 0 ? len : (i + 1) % len;
if(i == cur) {
break; // 如果又回到了本次搜索的起点,说明没有找到合适的空闲块
}
continue;
}
// 找到合适的空闲块
found = 1;
break;
}
// 没找到合适的空闲块
if(found == 0) {
return NULL;
}
// 在容差范围内,则整块分配
if(record->size - e <= n) {
record->tag = 1; // 转换为占用状态
// 否则,需要拆分空闲块
} else {
// 新建一条空闲记录
newRecord = (Record*) malloc(sizeof(Record));
newRecord->addr = record->addr + n;
newRecord->size = record->size - n;
newRecord->tag = 0;
newRecord->pre = -1;
newRecord->next = -1;
newRecord->oldAddr = -1;
// 插入新的空闲记录
ListInsert(&(space->recordList), len + 1, newRecord);
len += 1; // 链表长度增一
// 当前的空闲记录转为使用记录
record->size = n;
record->tag = 1;
}
// 构造一个虚拟地址
ptr = (int*) malloc(sizeof(int));
ListInsert(&(space->ptrList), ListLength(space->ptrList)+1, ptr);
*ptr = record->addr;
// 将游标cur指向下一条空闲记录
while(1) {
// 获取当前块的内存使用信息
GetElem(space->recordList, i, (ElemType*) &record);
// 跳过占用块
if(record->tag == 1) {
// 前进到下一条记录,注意是循环的
i = (i + 1) % len == 0 ? len : (i + 1) % len;
if(i == cur) {
break; // 如果又回到了本次搜索的起点,说明没有找到合适的空闲块
}
continue;
}
// 存储下一个可用空闲块的位置(不管找没找到都存储)
space->cur = i;
break;
}
return ptr; // 返回可用内存的虚拟地址
}
// 释放虚拟地址p处的内存
void FreeMemory(Space* space, int* p) {
int i, len;
Record* record;
// 获取当前内存记录的长度
len = ListLength(space->recordList);
// 遍历所有内存记录,查找待释放内存所在的记录
for(i = 1; i <= len; i++) {
// 获取当前块的内存使用信息
GetElem(space->recordList, i, (ElemType*) &record);
// 跳过不匹配的记录
if(*p != record->addr) {
continue;
}
// 如果该记录本来就是空闲,则直接返回
if(record->tag == 0) {
return;
}
// 将使用记录更改为空闲
record->tag = 0;
// 将游标指向刚刚回收的内存
space->cur = i;
// 将该地址对应的内存处的数据抹掉(这是个可选操作)
memset(getPtr(*space, p), 0, record->size*BYTE);
// 查找该虚拟地址在地址表中的位置
for(i = 0; i < space->ptrList.length; i++) {
if(space->ptrList.elem[i] == p) {
break;
}
}
// 删除该虚拟地址(用链表后面的数据覆盖前面的数据)
memmove(&(space->ptrList.elem[i]), &(space->ptrList.elem[i + 1]), (space->ptrList.length - i - 1) * sizeof(void*));
space->ptrList.length -= 1;
return;
}
}
// 将虚拟地址addr转换为底层的真实地址
void* getPtr(Space space, const int* addr) {
if(addr == NULL) {
return NULL;
} else {
if(*addr < 0 || *addr > MAXSIZE) {
return NULL;
}
return space.base + (*addr * BYTE);
}
}
// 打印内存布局,显示格式为:首地址 块大小 标志域 | 前驱 后继 | 数据
void PrintMemoryLayout(Space space) {
Record* record;
int i, len;
char* data;
len = ListLength(space.recordList);
for(i = 0; i < len; i++) {
record = static_cast<Record*>(space.recordList.elem[i]);
printf("%2d ", record->addr);
printf("%2d ", record->size);
printf("%1d ", record->tag);
// 接下来输出前驱和后继信息
printf(" | ");
if(record->tag == 0) {
printf("\n");
continue;
}
if(record->pre == -1) {
printf(" ");
} else {
printf("%2d ", record->pre);
}
if(record->next == -1) {
printf(" ");
} else {
printf("%2d ", record->next);
}
// 接下来输出该内存中的数据,这里只输出第一个字节中存储的数据,只是用于测试
printf("");
data = (char*)calloc(1, sizeof(char));
memmove(data, getPtr(space, &(record->addr)), 1);
printf("%d", *data);
printf("\n");
}
printf("\n");
}

View File

@@ -0,0 +1,64 @@
/*=======================
* 存储管理适用于习题8.16
========================*/
#ifndef MEMORYMANAGER_H
#define MEMORYMANAGER_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "SqList.h"
#define MAXSIZE 100 // 最大内存量
#define e 0 // 容差
#define BYTE sizeof(char) // 1个字节
// 内存记录类型定义
typedef struct {
int addr; // 新地址域,存储内存块的当前最新地址,初始值是空间首地址
int size; // 块大小
int tag; // 标志域,指示内存是正在使用(1)还是空闲状态(0)
/* 存储块之间的链接关系,这与用户在虚拟地址层看到的链接关系可能并不一致 */
int pre; // 前驱
int next; // 后继
int oldAddr; // 进行存储紧缩后的旧地址
} Record;
// 可用空间类型定义
typedef struct {
void* base; // 内存起始地址
SqList recordList; // 内存使用记录
int cur; // 记录空闲块的内存记录的位置初值为1
/*
* 记录虚拟地址。
* 这其实是在模拟操作系统的行为:
* 用户使用的地址跟硬件的实际地址可能并不一致,所以需要操作系统构造一个虚拟地址集。
*/
SqList ptrList;
} Space;
// 初始化一片可用的存储
void InitSpace(Space* space);
// 分配大小为n的内存并返回指向该内存的虚拟地址
int* AllocMemory(Space* space, int n);
// 释放虚拟地址p处的内存
void FreeMemory(Space* space, int* p);
// 将虚拟地址addr转换为底层的真实地址
void* getPtr(Space space, const int* addr);
// 打印内存布局,显示格式为:首地址 块大小 标志域 | 前驱 后继 | 数据
void PrintMemoryLayout(Space space);
#endif

View File

@@ -0,0 +1,174 @@
/*=============================
* 线性表的顺序存储结构(顺序表)
*
* 包含算法: 2.3、2.4、2.5、2.6
=============================*/
#include "SqList.h"
/*
* ████████ 算法2.3 ████████
*
* 初始化
*
* 初始化成功则返回OK否则返回ERROR。
*/
Status InitList(SqList* L) {
// 分配指定容量的内存如果分配失败则返回NULL
(*L).elem = (ElemType*) malloc(LIST_INIT_SIZE * sizeof(ElemType));
if((*L).elem == NULL) {
// 存储内存失败
exit(OVERFLOW);
}
(*L).length = 0; // 初始化顺序表长度为0
(*L).listsize = LIST_INIT_SIZE; // 顺序表初始内存分配量
return OK; // 初始化成功
}
/*
* 计数
*
* 返回顺序表包含的有效元素的数量。
*/
int ListLength(SqList L) {
return L.length;
}
/*
* 取值
*
* 获取顺序表中第i个元素将其存储到e中。
* 如果可以找到返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数但这不符合编码的通用约定。
* 通常i的含义应该指索引即从0开始计数。
*/
Status GetElem(SqList L, int i, ElemType* e) {
// 因为i的含义是位置所以其合法范围是[1, length]
if(i < 1 || i > L.length) {
return ERROR; //i值不合法
}
*e = L.elem[i - 1];
return OK;
}
/*
* ████████ 算法2.4 ████████
*
* 插入
*
* 向顺序表第i个位置上插入e插入成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListInsert(SqList* L, int i, ElemType e) {
ElemType* newbase;
ElemType* p, * q;
// 确保顺序表结构存在
if(L == NULL || (*L).elem == NULL) {
return ERROR;
}
// i值越界
if(i < 1 || i > (*L).length + 1) {
return ERROR;
}
// 若存储空间已满,则增加新空间
if((*L).length >= (*L).listsize) {
// 基于现有空间扩容
newbase = (ElemType*) realloc((*L).elem, ((*L).listsize + LISTINCREMENT) * sizeof(ElemType));
if(newbase == NULL) {
// 存储内存失败
exit(OVERFLOW);
}
// 新基址
(*L).elem = newbase;
// 存的存储空间
(*L).listsize += LISTINCREMENT;
}
// q为插入位置
q = &(*L).elem[i - 1];
// 1.右移元素,腾出位置
for(p = &(*L).elem[(*L).length - 1]; p >= q; --p) {
*(p + 1) = *p;
}
// 2.插入e
*q = e;
// 3.表长增1
(*L).length++;
return OK;
}
/*
* ████████ 算法2.5 ████████
*
* 删除
*
* 删除顺序表第i个位置上的元素并将被删除元素存储到e中。
* 删除成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListDelete(SqList* L, int i, ElemType* e) {
ElemType* p, * q;
// 确保顺序表结构存在
if(L == NULL || (*L).elem == NULL) {
return ERROR;
}
// i值越界
if(i < 1 || i > (*L).length) {
return ERROR;
}
// p为被删除元素的位置
p = &(*L).elem[i - 1];
// 1.获取被删除元素
*e = *p;
// 表尾元素位置
q = (*L).elem + (*L).length - 1;
// 2.左移元素,被删除元素的位置上会有新元素进来
for(++p; p <= q; ++p) {
*(p - 1) = *p;
}
// 3.表长减1
(*L).length--;
return OK;
}
/*
* 遍历
*
* 用visit函数访问顺序表L
*/
void ListTraverse(SqList L, void(Visit)(ElemType)) {
int i;
for(i = 0; i < L.length; i++) {
Visit(L.elem[i]);
}
printf("\n");
}

View File

@@ -0,0 +1,95 @@
/*=============================
* 线性表的顺序存储结构(顺序表)
*
* 包含算法: 2.3、2.4、2.5、2.6
=============================*/
#ifndef SQLIST_H
#define SQLIST_H
#include <stdio.h>
#include <stdlib.h> // 提供malloc、realloc、free、exit原型
#include "Status.h" //**▲01 绪论**//
/* 宏定义 */
#define LIST_INIT_SIZE 100 // 顺序表存储空间的初始分配量
#define LISTINCREMENT 10 // 顺序表存储空间的分配增量
/* 顺序表元素类型定义 */
typedef void* ElemType;
/*
* 顺序表结构
*
* 注elem在使用前需要先为其分配内存且元素从elem[0]处开始存储
*/
typedef struct {
ElemType* elem; // 顺序表存储空间的基址(指向顺序表所占内存的起始位置)
int length; // 当前顺序表长度(包含多少元素)
int listsize; // 当前分配的存储容量(可以存储多少元素)
} SqList;
/*
* ████████ 算法2.3 ████████
*
* 初始化
*
* 初始化成功则返回OK否则返回ERROR。
*/
Status InitList(SqList* L);
/*
* 计数
*
* 返回顺序表包含的有效元素的数量。
*/
int ListLength(SqList L);
/*
* 取值
*
* 获取顺序表中第i个元素将其存储到e中。
* 如果可以找到返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数但这不符合编码的通用约定。
* 通常i的含义应该指索引即从0开始计数。
*/
Status GetElem(SqList L, int i, ElemType* e);
/*
* ████████ 算法2.4 ████████
*
* 插入
*
* 向顺序表第i个位置上插入e插入成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListInsert(SqList* L, int i, ElemType e);
/*
* ████████ 算法2.5 ████████
*
* 删除
*
* 删除顺序表第i个位置上的元素并将被删除元素存储到e中。
* 删除成功则返回OK否则返回ERROR。
*
*【备注】
* 教材中i的含义是元素位置从1开始计数
*/
Status ListDelete(SqList* L, int i, ElemType* e);
/*
* 遍历
*
* 用visit函数访问顺序表L
*/
void ListTraverse(SqList L, void (Visit)(ElemType));
#endif

View File

@@ -0,0 +1,17 @@
| 首地址 | 块大小 | 标志域 | 前驱指针 | 后继指针 |
| :----: | :----: | :----: | :----: | :----: |
| 0 | 5 | 1 | -1 | 10 |
| 5 | 5 | 0 | -1 | -1 |
| 10 | 5 | 1 | 15 | 40 |
| 15 | 10 | 1 | -1 | -1 |
| 25 | 5 | 0 | -1 | -1 |
| 30 | 10 | 0 | -1 | -1 |
| 40 | 5 | 1 | 45 | 65 |
| 45 | 10 | 1 | -1 | -1 |
| 55 | 5 | 0 | -1 | -1 |
| 60 | 5 | 0 | -1 | -1 |
| 65 | 5 | 1 | 85 | -1 |
| 70 | 5 | 0 | -1 | -1 |
| 75 | 10 | 0 | -1 | -1 |
| 85 | 5 | 1 | 90 | -1 |
| 90 | 10 | 1 | -1 | -1 |

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@@ -0,0 +1,126 @@
#include <stdio.h>
#include "Status.h"
#include "SqStack.h"
/* 宏定义 */
#define MAXSIZE 200 // 内存总大小
/* 空闲空间类型定义 */
typedef struct {
void* base; // 可用空间内存的起始地址
int size; // 空闲空间大小(按字节计数)
SqStack block; // 已经使用的内存块大小,先申请的内存信息先入栈
} Space;
// 初始化内存空间及内存信息栈
void InitSpace(Space* space);
// 打印内存布局,查看当前内存的使用情况
void PrintMemoryLayout(Space space);
// 申请大小为n的内存块返回申请到的内存起始地址
void* Algo_8_11(Space* space, int n);
// 回收内存,遵循"最后分配者最先释放"的原则
Status Algo_8_12(Space* space);
int main(int argc, char* argv[]) {
Space space;
int s[5] = {50, 50, 150, 100, 50}; // 定义内存申请数量
int i;
// 初始化内存
InitSpace(&space);
PrintMemoryLayout(space);
printf("\n");
// 申请/释放内存
for(i = 0; i < 5; i++) {
printf("申请 %d 个内存后...\n", s[i]);
Algo_8_11(&space, s[i]);
PrintMemoryLayout(space);
printf("\n");
}
return 0;
}
// 初始化一块空闲空间
void InitSpace(Space* space) {
// 申请MAX字节的空间(假设总能初始化成功)
space->base = malloc(MAXSIZE);
space->size = MAXSIZE;
// 初始化内存信息栈
InitStack(&(space->block));
}
// 打印内存布局,查看当前内存的使用情况
void PrintMemoryLayout(Space space) {
int i;
for(i = 1; i <= MAXSIZE; i++) {
if(i <= space.size) {
printf("");
} else {
printf("");
}
// 预定每隔20个换一下行
if(i % 20 == 0) {
printf("\n");
}
}
if(MAXSIZE % 20 != 0) {
printf("\n");
}
}
// 申请大小为n的内存块返回申请到的内存起始地址
void* Algo_8_11(Space* space, int n) {
/*
* 如果可用的内存数量少于申请的内存数量,
* 则需要释放最近申请使用的内存以腾出空间。
*/
while(space->size < n && Algo_8_12(space) == OK) {
// 直到空闲内存满足申请要求,或者全部占用内存已经释放,才会退出循环
}
// 如果没有足够的内存可用,则返回空指针
if(space->size < n) {
return NULL;
}
// 可用空间减小
space->size -= n;
// 记下成功申请到的内存
Push(&(space->block), n);
// 返回申请到的内存起始地址(VS里运行需要指定指针类型这里设定为一个字节)
return (char*)(space->base) + space->size;
}
// 回收内存,遵循"最后分配者最先释放"的原则
Status Algo_8_12(Space* space) {
int e;
// 已经没有可释放内存时返回ERROR
if(StackEmpty(space->block)) {
return ERROR;
}
// 将最后申请的内存释放掉
Pop(&(space->block), &e);
// 可用空间增大
space->size += e;
return OK;
}

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