看数据结构写代码(50)伙伴系统

伙伴系统 是一种 只 可以 分配 2的 幂次方 个 空间的 ,回收 内存 时 只 合并 “伙伴空间” 的一种 动态内存管理方式。

例如 一个 空间 大小 为 64 的 内存,伙伴 系统 为 这 64 的内存  建立 一组 双向循环 链表,分别 管理着  2的 0 次方,2的1 次方幂,2的 2 次方幂。。。2的6次方幂的 可用空间。

即使 我们 只想分配 一个 大小 为3的 空间,系统 却 只能 返回 一个 内存 大小 为 4(2的2次方)的 一个空间。

系统 在 初始化的 时候 ,并不是 为 每一个 链表 都 放入 一些 可用空间 。而是 在 2的 6次方幂 插入 一个 空间节点,大小 为64

当我们 想 要 分配 内存的时候,会 查找 最近 内存大小的 链表,如果 链表 有 可用空间,取 第一个 空间。否则 顺次 查找 可用空间。

假设 我们 要在 这个 64 的 空间 里,分配 一个 大小 为 3 的内存,系统 怎么 分配呢?

系统 会 从 第一个链表 一直 找,直到 找到 2的 6次方的 链表。系统 会 将 前 4个 空间 分配 给用户。可是 其余的 60个空间 ,不能 再 插入到 2的 6次方 的链表中了。只能 插入到

比 它小的链表中去。

具体的 分配 方式 如下:4    4    8   16   32 。

前4个 分给用户 使用,后 面的 4 8 16 32  依次 插入 到 2的 2次方幂,2的 3次方,4次方,5次方幂的链表中去。

这 有一个 规则:

1. 用户 空间 的 邻接空间 总是 被 拆分成  一个 跟 用户 空间 大小 一样的 空间。这个 空间 就是 上面 所有的 “伙伴空间”。

2. 再 下一个空间 依次 是 用户空间的 2倍,4倍,8倍,。。。直到 被拆分空间 一半。 例子的 拆分空间 为 64,它的一半 为 32。

所以 可以 算出:64 是 如何 被 拆分的: 4(用户空间) 4(伙伴空间) 8   16  32 

当我们 回收空间时,首先 会 查看 这个空间的 伙伴空间 是否 为 空闲。如果空闲 会 合并,并从 链表中 删除 伙伴空间。。然后 会 再次 查看 合并后的 空间。直至 无法 合并。如果 不空闲,插入到 合适的 链表中去。

伙伴空间 就是 上面 所说的 “4” ,必须 跟 回收空间 空间大小一样 ,而且 是 从 同一个 大块 内存 分配 出去的,

有一个公式可以求出 伙伴空间:

例如 释放 上面 的 空间, 会查找 伙伴空间 ,伙伴空间 空闲,合并空间 成为 一个 大小 为8 的空间,并从 2 的 2 次方 链表中 删除 伙伴空间,然后 继续 查找。 继续删除。。最终 会 合并 成 一个 在 2的 6次幂链表中的 一个 空间。 和 初始的情况一样。

下面的 代码 我 没有 按照 书上的代码来写,按照 自己的思路写的,(书上的代码看的我有点 糊涂)。如有误,望指出。

代码如下:

// buddyAlloc.cpp : 定义控制台应用程序的入口点。
//伙伴系统

#include "stdafx.h"
#include <cstdlib>
#include <cmath>

#define MAX_SIZE	64//最大空间
#define LIST_LEN	7//表头数组长度
#define MAX_INDEX	6//表头数组最大下标
struct BuddyNode{
	BuddyNode * preLink;
	BuddyNode * nextLink;
	int k;//只能分配2 的k次幂
	int tag;//0 空闲, 1 占用
};

typedef struct BuddyHead{
	BuddyNode * head;
	int nodeSize;
}FreeList[LIST_LEN];

static BuddyNode * freeSpace = NULL;//为了释放内存
static BuddyNode * userSpace[MAX_SIZE] = {NULL};
static int userCount = 0;

void initFreeList(FreeList list){
	for (int i = 0; i < LIST_LEN; i++){
		list[i].head = NULL;
		list[i].nodeSize = (int)pow(2.0,i);
	}
	//分配最大空间
	BuddyNode * p = (BuddyNode*)malloc(sizeof(BuddyNode) * MAX_SIZE);
	if (p != NULL){
		p->k = MAX_INDEX;
		p->tag = 0;
		p->nextLink = p->preLink = p;//双向循环链表
	}
	freeSpace = list[MAX_INDEX].head = p;
}

void destoryFreeList(FreeList list){
	for (int i = 0; i < LIST_LEN; i++){
		list[i].head = NULL;
	}
	free(freeSpace);
}

BuddyNode * buddyAlloc(FreeList list,int size){
	bool isFirst = true;
	int k = -1;//分配空间的k值
	int needK = -1;//需要分配空间的k值.
	//查找第一个满足条件的空间.
	for (int i = 0; i < LIST_LEN; i++){
		BuddyHead head = list[i];
		if (head.nodeSize >= size){
			if (isFirst){
				needK = i;
				isFirst = false;
			}
			if (head.head != NULL){//找到可分配的空间了
				k = i;
				break;
			}
		}
	}
	BuddyNode * freeNode = NULL;
	if (k == -1){
		return NULL;//找不到满足条件的空间了..
	}
	else{
		//设置这个空间 被占用,并且 更换 表头元素..
		freeNode = list[k].head;
		freeNode->k = needK;
		freeNode->tag = 1;//设置 成 已占用..
		list[k].head = freeNode->nextLink;//重新设置表头..
		if (freeNode->preLink == freeNode->nextLink){//删除这个空间后,成为空表.
			list[k].head = NULL;
		}
		else{//删除这个节点.
			freeNode->preLink->nextLink = freeNode->nextLink;
			freeNode->nextLink->preLink = freeNode->preLink;
		}
		//剩余空间 依次 插在 needK 和 k-1 表里.
		for (int i = needK; i < k ; i++){//从高位开始分配..
			int index = (int)pow(2.0,i);
			BuddyNode * p = freeNode + index;
			p->k = i;
			p->tag = 0;
			BuddyNode * head = list[i].head;
			if (head != NULL){
				p->preLink = head->preLink;
				p->nextLink = head;
				p->preLink->nextLink = head->preLink = p;
			}
			else{
				p->preLink = p->nextLink = p;
			}
			list[i].head = p;//重新设置表头..
		}
	}
	userSpace[userCount++] = freeNode;
	return freeNode;
}

void userSpaceFree(BuddyNode * node){
	for (int i = 0; i < userCount; i++){
		if (userSpace[i] == node){
			userSpace[i] = NULL;
		}
	}
}

//伙伴算法 回收...
void buddyReclaim(FreeList list,BuddyNode * node){
	userSpaceFree(node);
	while (node->k < MAX_INDEX){//循环查找伙伴,k值达到 MAX_INDEX 不需要 寻找...
		int sub = node - freeSpace;//计算出 具体坐标位置
		BuddyNode * buddy = NULL;
		int i = (int)pow(2.0,node->k + 1);
		bool isNext = true;//伙伴是不是在后
		if(sub % i == 0){//伙伴在后
			buddy = node + i/2;
		}
		else{//伙伴在前.
			buddy = node - i/2;
			isNext = false;
		}
		if (buddy->tag == 0 ){//伙伴空闲,合并
			//首先从列表里释放 buddy
			if (buddy->preLink == buddy->nextLink){//表中 只有一个节点,释放后,成为空表
				list[buddy->k].head = NULL;
			}
			else{//删除节点
				buddy->preLink->nextLink = buddy->nextLink;
				buddy->nextLink->preLink = buddy->preLink;
				list[buddy->k].head = buddy->nextLink;
			}
			if (isNext == false){
				node = buddy;
			}
			node->k ++;
		}
		else{//伙伴空间被占用..
			break;
		}
	}
	BuddyNode * head = list[node->k].head;
	node->tag = 0;
	if (head == NULL){//表头为空
			node->preLink = node->nextLink = node;
	}
	else{
		node->nextLink = head;
		node->preLink = head->preLink;
		node->preLink->nextLink = head->preLink = node;
	}
	list[node->k].head = node;
}

void printList(FreeList list){
	printf("------------------------打印伙伴列表-------------------\n");
	for (int i = 0; i < LIST_LEN; i++){
		BuddyNode * head = list[i].head;
		if (head){
			printf("首地址空间为:%0x,表的前驱:%0x,后继:%0x,表空间大小是2的%d次方\n",head,head->preLink,head->nextLink,head->k);
		}
	}
	printf("------------------------用户空间-------------------\n");
	for (int i = 0; i < userCount; i++){
		BuddyNode * us = userSpace[i];
		if (us != NULL){
			printf("首地址空间为:%0x,表空间大小是2的%d次方\n",us,us->k);
		}
	}
	printf("\n");
}

int _tmain(int argc, _TCHAR* argv[])
{
	FreeList list;
	initFreeList(list);
	printList(list);
	printf("---------------分配一个1空间之后--------------\n");
	BuddyNode * s1_1 = buddyAlloc(list,1);
	printList(list);
	printf("---------------分配一个1空间之后--------------\n");
	BuddyNode * s1_2 = buddyAlloc(list,1);
	printList(list);
	printf("---------------分配一个29空间之后--------------\n");
	BuddyNode * s29 = buddyAlloc(list,29);
	printList(list);
	printf("---------------释放一个1空间之后--------------\n");
	buddyReclaim(list,s1_2);
	printList(list);
	printf("---------------释放一个1空间之后--------------\n");
	buddyReclaim(list,s1_1);
	printList(list);
	destoryFreeList(list);
	return 0;
}

完整代码工程文件网盘地址:点击打开链接

运行截图:

时间: 2024-10-01 05:16:11

看数据结构写代码(50)伙伴系统的相关文章

看数据结构写代码(32) 赫夫曼树编码以及译码

杂谈:最近有点慵懒,不好不好.好几天都没写代码,原本准备上星期完结 树 这一章节的.现在 又耽误了.哎.要抓紧时间啊. 下面直接上代码: 可以到我的网盘下载源代码,或者 直接拷贝下面的源代码 运行 网盘地址:点击打开链接 // HuffmanTree.cpp : 定义控制台应用程序的入口点. //哈弗曼编码,译码 #include "stdafx.h" #include <stdlib.h> #include <cstring> enum E_State { E

看数据结构写代码(44) 判断无向图是否有环路

在 看 严蔚敏的 数据结构 一书 7.5小节时,书上 说" 判断有向图是否存在环要不无向图复杂.对于无向图来说,深度优先遍历过程中遇到回边(即指向已访问过的顶点的边),则必定存在环路". 看的不明白,所以 网上 百度了一下. 有了思路:故写下算法 和思路,以便以后 温故. 思路: 1.一个n个顶点,e条边的 无向图,若 e>= n,必有环路. 2.若 e < n ,需要 深度 遍历,并把 父节点传入 参数中,如果 遇到 一个 节点 被访问过 并且 不是 父节点,那么 就有环

看数据结构写代码(67) 置换 _ 选择排序(完结篇)

杂谈: 严蔚敏版<数据结构(C语言版)> 一书 终于看完了.这是 一个完结,也是 一个新的开端.<算法导论> 已到手. 置换选择排序的思想 是 将 归并段 尽量 变的 更大,而不是根据 内存 大小 限制在 固定的 大小. 这样 可以 利用赫夫曼树 来 进行 最优归并树,从而 使 外存 读写次数 最少. 下面给出 具体 代码:欢迎指出代码不足. // Replace_Selcetion.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q

看数据结构写代码(15)链式队列的实现

队列 和 栈 是 一种 受限制的 线性表.所以 他们的 实现方式 都 相差 无几.之前有过  链栈 和 链式线性表 的 实现经验,自然 写 链队 ,也毫无问题. 下面详细讲解每一段代码 的技术要点 下面是队列节点的数据结构 struct QueueNode { ElementType data; QueueNode * next; }; //生成一个节点 QueueNode * queueNodeMake(ElementType data){ QueueNode * pNode = (Queue

看数据结构写代码(35) 图的邻接矩阵表示法

杂谈:最近清明小长假,好好的放松了一下.节前 和 节后 都有点 松懈.不好,不好.贵在坚持.加油. 图的邻接矩阵表示法是用 两个数组 来表示 图的数据结构.一个是顶点数组,另一个是邻接矩阵数组.邻接矩阵 里存放着 顶点的关系. 用邻接矩阵表示图,在 看 顶点之间 是否有边,或者 求顶点的度等操作时比较简单.但空间浪费巨大,在插入,删除 顶点 和边 操作时 需要 移动大量数据,造成不便.所以在插入删除比较多,节点数比较多的时候 不宜 使用这种结构. 下面上代码: 源代码网盘地址:点击打开链接 //

看数据结构写代码(43) 关节点

首先 说明一下 概念问题: 关节点 :如果删除无向 图中的一个顶点,以及与顶点相关的边,把 图的 一个连通 分量 变成 两个 以上的 连通 分量.这样的顶点叫做关节点. 没有 关节点的 无向图,叫做 重连通图.重连通图中 任意 两个顶点 至少 存在 两条以上的 通路. 如果 删除 连通图上的 k个 节点,才能 破坏 他的连通性,那么 这个连通图的 连通度 为k. 下面的算法 是 求 连通图的 关节点,并没有 考虑 求图的 关节点,不过 要 改成 图的 关节点 也不难,只要 加 一个 for i

看数据结构写代码(57) AVL树的删除

上一节 已经说了 AVL树的插入 操作,可是 只有 插入,没有删除,怎么能叫 动态 查找表呢. 呵呵,博主 赶紧 去 研究了一番.下面 是成果: AVL树的删除 大致 分为 两大块: 1. 查找节点 并 删除 2. 保持 删除 后 平衡因子的 影响 1. 首先 找到 这个 节点,如果 节点 不存在,直接 退出 函数 if (*tree == NULL){//没找到 return false; } 2.如果 存在,分为 四种情况:(根 二叉 排序树的 删除 类似) 1.节点 为 叶子 节点,直接

看数据结构写代码(63) 堆排序

// HeapSort.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <cstdlib> #define LIST_MAX_SIZE 100 //顺序表 struct sqList{ int base[LIST_MAX_SIZE]; int len; }; typedef sqList Heap;//顺序表作为堆排序的基本类型 //初始化顺序表 void initHeap(Heap * list,int * arr

看数据结构写代码(8)顺序栈的实现

欢迎指出 代码 不足之处 在写顺序栈的时候 犯了两个错误,:一个是 对栈的 认识不够清楚,栈顶指针的下一个位置为栈顶元素: 另一个是粗心,在用 realloc 分配内存的时候,忽略了元素本身的大小,只写了 元素的个数. 希望引以为戒. 上代码: // SqStack.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <stdlib.h> #include <cstdio> //故意将值设置的比较小,以测试