实验目的与要求
1、让学生理解动态内存分配的工作原理;
2、让学生应用指针、系统级编程的相关知识;
3、让学生应用各种动态内存分配器的实现方法;
实验原理与内容
(1)动态内存分配器基本原理
动态内存分配器维护着一个进程的虚拟内存区域,称为堆。分配器将堆视为一组不同大小的块的集合来维护,每个块就是一个连续的虚拟内存片,要么是已分配的,要么是空闲的。已分配的块显式地保留为供应用程序使用。空闲块可用来分配。空闲块保持空闲,直到它显式地被应用所分配。一个已分配的块保持已分配状态,直到它被释放,这种释放要么是应用程序显式执行的,要么是内存分配器自身隐式执行的。
分配器有两种基本风格:显式分配器和隐式分配器。两种风格都要求应用显式地分配块。它们的不同之处在于由哪个实体来负责释放已分配的块。
显式动态内存分配器要求应用显式地释放任何已分配的块。例如C程序通过调用malloc函数来分配一个块,通过调用free函数来释放一个块。其中malloc采用的总体策略是:先系统调用sbrk一次,会得到一段较大的并且是连续的空间。进程把系统内核分配给自己的这段空间留着慢慢用。之后调用malloc时就从这段空间中分配,free回收时就再还回来(而不是还给系统内核)。只有当这段空间全部被分配掉时还不够用时,才再次系统调用sbrk。当然,这一次调用sbrk后内核分配给进程的空间和刚才的那块空间一般不会是相邻的。
隐式动态内存分配器也叫做垃圾收集器,例如,诸如Lisp、ML、以及Java之类的高级语言就依赖垃圾收集来释放已分配的块。
(2)隐式空闲链表
对于带边界标记的隐式空闲链表分配器,一个块是由一个字的头部、有效载荷、可能的一些额外的填充,以及在块的结尾处的一个字的脚部组成的。头部编码了这个块的大小(包括头部和所有的填充),以及这个块是已分配的还是空闲的。如果我们强加一个双字的对齐约束条件,那么块大小就总是8的倍数,且块大小的最低3位总是0。因此,我们只需要内存大小的29个高位,释放剩余的3位来编码其他信息。在这种情况中,我们用其中的最低位(已分配位)来指明这个块是已分配的还是空闲的。头部后面就是应用调用malloc时请求的有效载荷。有效载荷后面是一片不使用的填充块,其大小可以是任意的。需要填充有很多原因。比如,填充可能是分配器策略的一部分,用来对付外部碎片。或者也需要用它来满足对齐要求。
我们将对组织为一个连续的已分配块和空闲块的序列,这种结构称为隐式空闲链表,是因为空闲块是通过头部中的大小字段隐含地连接着的。分配器可以通过遍历堆中所有的块,从而间接地遍历整个空闲块的集合。注意:此时我们需要某种特殊标记的结束块,可以是一个设置了已分配位而大小为零的终止头部。
Knuth提出了一种边界标记技术,允许在常数时间内进行对前面块的合并。这种思想是在每个块的结尾处添加一个脚部,其中脚部就是头部的一个副本。如果每个块包括这样一个脚部,那么分配器就可以通过检查它的脚部,判断前面一个块的起始位置和状态,这个脚部总是在距当前块开始位置一个字的距离。
(3)显式空闲链表
根据定义,程序不需要一个空闲块的主体,所以实现空闲链表数据结构的指针可以存放在这些空闲块的主体里面。显式空闲链表结构将堆组织成一个双向空闲链表,在每个空闲块的主体中,都包含一个pred(前驱)和succ(后继)指针。使用双向链表而不是隐式空闲链表,使首次适配的分配时间从块总数的线性时间减少到了空闲块数量的线性时间。不过,释放一个块的时间可以是线性的,也可能是个常数,这取决于空闲链表中块的排序策略。一种方法是用后进先出(LIFO)的顺序维护链表,将新释放的块放置在链表的开始处。另一种方法是按照地址顺序来维护链表,其中链表中每个块的地址都小于它后继的地址。
在本实验中,您将使用C语言编写一个显式动态内存分配器,我们鼓励您创造性地进行设计探索,以实现正确、高效和快速的分配器。
实验设备与软件环境
1.Linux操作系统—64位 Ubuntu 18.04
2. C编译环境(gcc)
3. 计算机
实验过程与结果(可贴图)
1、初始化内存管理系统的函数mm_init
通过mem_sbrk系统调用来分配初始的内存堆大小HEAP_INITSIZE。如果分配失败,函数返回-1。成功后,它初始化管理内存的各个数据结构:设置空闲块树的根节点为NULL,表示初始时树为空;内存块链表也初始化为空;通过设置边界标签,确保内存分配不会跨越预设的堆边界;初始化块分割策略的奇偶标志;最后,将除去边界标签占用空间之外的整个初始内存区域作为一个大的空闲块加入到空闲块管理队列中。如果一切顺利,函数返回0,表示初始化成功。
2、初始化堆mm_free,作用是释放指向动态分配内存的指针所指向的内存空间。函数首先获取要释放块的大小,并准备合并操作。接着,检查当前块前后是否有已释放的空间:如果后方有空闲块,则将其合并至当前块并调整总大小;如果前方有空闲块,则更新起始位置、合并大小,并同样向前合并。完成相邻空闲块的合并后,更新后的大型空闲内存块会被重新登记到内存管理器的空闲列表中,等待未来的分配请求。这一过程减少了内存碎片,提高了内存分配的效率。
2、内存重分配函数*mm_realloc:负责调整给定指针ptr指向的内存块的大小到size指定的大小,同时尽量保持原有数据的完整性。
① 如果输入的指针ptr为空,函数相当于执行一次新的内存分配,调用mm_malloc(size)来分配指定大小的内存,并返回新分配的内存地址。
② 如果请求的大小size为0,意味着希望释放内存而非重新分配,因此直接调用mm_free(ptr)释放内存并返回NULL。
③ 检查当前块的大小是否已经大于等于请求的大小加上8字节(可能是为了对齐或其他内部管理需要)。如果是,直接返回原指针,无需重新分配。
④ 如果下一个内存块是空闲的,并且合并后的大小能满足新的需求,函数会合并这两个块,更新大小标签,并返回原指针。
⑤ 如果下一个块是空闲的且紧邻内存区域边界,函数尝试扩展内存堆,通过调用mem_sbrk增加内存,并更新大小标签。
⑥ 如果当前块已经是最后一个块,无法直接扩展,函数会尝试增加内存堆的大小以满足新尺寸要求,然后调整当前块的大小并返回原指针。
⑦ 如果以上条件都不满足,即不能在当前位置调整大小或扩展,函数会在内存中另找一个合适的位置(通过调用mm_malloc(size)),复制原数据到新位置,释放旧内存,然后返回新内存地址。
3、自定义的内存分配函数mm_malloc:用于从内存堆中分配指定大小的内存空间。其工作流程涉及查找合适的空闲块、内存堆扩展、块分割及内存管理等。
函数首先根据请求的大小调整并确保内存对齐,然后尝试从现有的空闲块中分配。如果找不到合适的空闲块,则通过mem_sbrk系统调用扩大内存堆。在分配到足够大的块后,若块的剩余部分大于一定阈值,则会分割该块并将剩余部分重新加入空闲块管理结构中,以优化内存使用。最后,函数会设置新分配块的标签,表示该内存已被分配,并在调试模式下打印相关信息。
实验总结
在本次计算机系统基础实验中,我深入了解并实践了动态内存分配器的工作原理,特别是显式分配器的设计与实现。通过C语言,我在Ubuntu系统上编写了具备显式内存分配器,实现了内存的高效管理,从初始化、释放、重分配、调整大小直至优化分配一整套流程,解决了内存碎片化、资源不足、泄露、分配效率低效等问题,提升整体利用,实践展现了内存对指针、系统编程、内存管理的综合应用能力,实验的成果不仅在于实现了分配器本身,更在于对内存管理的深入理解与解决复杂问题的能力提升。
/*
* malloc.c: A Reasonably Efficient Best-Fit Allocator.
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include "memlib.h"
#include "mm.h"
/* Team Information */
team_t team = {
"Tree-based free list (CMU CS 213 students, Fall 2000)",
"Vinny Furia",
"vmf@andrew.cmu.edu",
"Nick Carter",
"nbc@andrew.cmu.edu"
};
//int split_parity = 1;
/* Compilation Options - to be removed eventually */
#define _DEBUG_ 0 /* Prints out lots of crap */
/* Typedefs for Heap Data Structures */
typedef struct List {
void** BLAH;
struct List* Prev;
struct List* Next;
void** BLAH2;
} List;
typedef struct Node {
struct Node* Left;
struct Node* Right;
int Color;
struct Node* Parent;
} Node;
/* Named Constants */
#define HEAP_INITSIZE 8232 /* multiple of 8 */
#define HEAP_GROWSIZE 4096 /* multiple of 8 */
#define REALLOC_GROWSIZE 2048 /* multiple of 8 */
//#define REALLOC_BUBBLE 128 /* blocksize */
//#define REALLOC_BUBBLEINCREMENT 128
#define RED 1
#define BLACK 0
#define FALSE 0
/* Masks for Boundary Tags */
#define SIZE_MASK 0xFFFFFFF8
#define TREE_MASK 4
#define BLOB_MASK 2
#define FREE_MASK 1
#define IN_TREE (FREE_MASK|TREE_MASK)
#define IN_BLOB (FREE_MASK|BLOB_MASK)
#define ALLOCATED 0
/* Named Heap Locations */
#define treeroot ((Node**)(char *)mem_heap_lo())
#define blobroot ((List**)(char *)mem_heap_lo()+1)
#define boundtag_lo (((int*)((char *)mem_heap_lo()+16)))
#define boundtag_hi (((int*)((char *)mem_heap_hi()-3 )))
#define split_parity (*((int*)(char *)mem_heap_lo()+12))
/* Macros for Boundary Tags */
// The following 2 are just for use in definitions
#define __LowTag(p) (*((int*)(p)-1))
#define __HiPrevTag(p) (*((int*)(p)-2))
// The next few can be safely used with all block pointers
#define Size(p) (__LowTag(p) & SIZE_MASK)
#define IsFree(p) (__LowTag(p) & FREE_MASK)
#define IsInBlob(p) (__LowTag(p) & BLOB_MASK)
#define IsInTree(p) (__LowTag(p) & TREE_MASK)
#define NextBlock(p) ((char *)(p) + Size(p))
#define PrevFree(p) (__HiPrevTag(p) & FREE_MASK)
#define NextFree(p) (IsFree(NextBlock(p)))
// PrevBlock(p) is undefined for the first addressable block.
#define PrevBlock(p) ((char *)(p) - (__HiPrevTag(p) & SIZE_MASK))
/* Tree Node Macros */
#define IsRed(p) ((p!=NULL) && (((Node*)p)->Color == RED))
/*
* Invariants:
* - The word starting at (mem_heap_lo()) is a pointer to the root
* node of the freetree.
* If this value is null, then there are no free blocks.
* - The word starting at (mem_heap_lo()+8) is a fake upper boundary
* tag, with size 0, and flagged as allocated.
* - The word starting at (deseg_hi-3) is a fake lower boundary
* tag, with size 0, and flagged as allocated.
* - Every block has a "front" and "rear" tag. This tag is 4 bytes
* (1 word) being an integer of the size of the block.
* The semantics of these tags are as follows:
* - By masking off the least significant 3 bits, we get
* the size of the block in bytes.
* - Combinations of the last 3 bits are interpreted as follows:
* 0 0 0 => Block is allocated
* 1 1 1 => Block is a red node in the free tree.
* 1 0 1 => Block is a black node in the free tree.
* 0 0 1 => Block is on the trailing list of some
* node in the free tree.
* 0 1 1 => Block is free but not in the tree; instead
* it is in the doubly-linked list of recently
* freed nodes (the blob).
* - Free blocks have front and rear tags as described above, and
* the front tag is followed by three pointers. These pointers are
* (in order): Left Child, Right Child, List Next. Each of these
* is one word in length (4 bytes). All Free blocks will be
* maintained within a Red-Black tree with blocks of the same
* size being in an address ordered linked list.
* - Allocated Blocks will have front and rear tags as described
* previously with block data between these tags.
*
* - At a
*
* Policies:
*
* - Free blocks are coalesced immediately and added to the freetree.
* - Allocation is done best-fit. When there are several candidates for
* the best-fit, then the lowest-addressed block is chosen.
*
* Conventions:
*
* - Block pointers are treated as (int*)'s
*/
void setTags (void* block, size_t size, int flags)
{
int* tag1 = (int*)block - 1;
int* tag2 = (int*)((char *)block+size)-2;
*tag1 = *tag2 = (size | flags);
}
/*
* void left_rotate(Node* x)
*
* x --> y
* / \ / \
* T1 y --> x T3
* / \ / \
* T2 T3 --> T1 T2
*
* Precondition: x must be non-null with a non-null right child.
*/
void left_rotate (Node* x) {
Node* y = x->Right;
x->Right = y->Left;
if ( y->Left != NULL )
y->Left->Parent = x;
//Set the parent to point to y instead of x
y->Parent = x->Parent;
if ( x->Parent == NULL )
*treeroot = y;
else
if ( x == x->Parent->Left )
//x was on the left of its parent
x->Parent->Left = y;
else
//x must have been on the right
x->Parent->Right = y;
y->Left = x;
x->Parent = y;
}
/*
* void right_rotate(Node* x)
*
* x --> y
* / \ / \
* y T3 --> T1 x
* / \ / \
* T1 T2 --> T2 T3
*
* Precondition: x must be non-null with a non-null left child.
*/
void right_rotate (Node* x) {
Node* y = x->Left;
x->Left = y->Right;
if (y->Right != NULL)
y->Right->Parent = x;
y->Parent = x->Parent;
// Set the parent to point to y instead of x
if (x->Parent == NULL)
*treeroot = y;
else
if (x == x->Parent->Left)
// x was on the left of its parent
x->Parent->Left = y;
else
// x must have been on the right
x->Parent->Right = y;
y->Right = x;
x->Parent = y;
}
/* Color Functions */
int isRed (Node* x) {
return (x != NULL) && (x->Color == RED);
}
int isBlack (Node* x) {
return (x == NULL) || (x->Color == BLACK);
}
void setblack (Node* x) {
if (x!=NULL)
x->Color = BLACK;
}
void setred (Node* x) {
// NULL nodes are never red.
x->Color = RED;
}
/*
* int isLess(Node* x, Node* y)
*
* This expression defines the order by which
* the tree is constructed. x and y may not be NULL.
*
*/
int isLess (Node* x, Node *y) {
return (Size(x) < Size(y)) || ((Size(x) == Size(y)) && x<y);
}
/*
* int tree_insert(Node* x)
*
* Preconditions:
* - x should be an established node
* (i.e. size flags, IN_TREE set) that
* does not appear already in the tree.
*
* Postconditions:
* Inserts the node into the tree as follows:
* - If the tree is currently empty, then we set x
* to be the root, color it black, and return 0.
* - Otherwise, we insert x as a leaf, color it red,
* and return 1.
*
*/
int tree_insert (Node* x) {
Node* current = *treeroot;
//Empty tree --> update root pointer
if (current == NULL) {
*treeroot = x;
x->Parent = NULL;
x->Right = x->Left = NULL;
x->Color = BLACK;
return 0;
}
//Non-empty tree --> insert as leaf
while(1) {
if (isLess(x,current)) {
//x belongs in the left child of current node
if (current->Left != NULL)
current = current->Left;
else {
current->Left = x;
x->Parent = current;
x->Right = x->Left = NULL;
x->Color = RED;
return 1;
}
}
else {
//x belongs in the right child of current node
if (current->Right != NULL)
current = current->Right;
else {
current->Right = x;
x->Parent = current;
x->Right = x->Left = NULL;
x->Color = RED;
return 1;
}
}
}
}
void freetree_insert (void* ptr, size_t size) {
Node* x;
Node* y;
//Write the tags for the block
setTags(ptr, size, IN_TREE);
x = (Node*)ptr;
//Do a tree insertion
if (tree_insert(x) == 0){
// No further work needed.
return;
}
/* Move up the tree to restore the red/black property. */
/* Invariant: x is red, and red/black properties are every- */
/* where satisfied, except maybe between x and */
// x->Parent. */
while ( (x->Parent != NULL)
&& (isRed(x->Parent))
&& (x->Parent->Parent != NULL)) {
if ( x->Parent == x->Parent->Parent->Left ) {
/* If x's parent is a left, y is x's right 'uncle' */
y = x->Parent->Parent->Right;
if ( isRed(y) ) {
/* case 1 - change the colours */
setblack(x->Parent);
setblack(y);
setred(x->Parent->Parent);
/* Move x up the tree */
x = x->Parent->Parent;
}
else {
/* y is a black node */
if ( x == x->Parent->Right ) {
/* and x is to the right */
/* double-rotate . . . */
left_rotate( x->Parent );
right_rotate( x->Parent );
setblack( x->Left );
}
else
{
/* single-rotate */
setblack(x);
x = x->Parent;
right_rotate( x->Parent );
}
}
}
else {
/* If x's parent is a right, y is x's left 'uncle' */
y = x->Parent->Parent->Left;
if ( isRed(y) ) {
/* case 1 - change the colours */
setblack(x->Parent);
setblack(y);
setred(x->Parent->Parent);
/* Move x up the tree */
x = x->Parent->Parent;
}
else {
/* y is a black node */
if ( x == x->Parent->Left ) {
/* and x is to the left */
/* double rotate */
right_rotate( x->Parent );
left_rotate( x->Parent );
setblack(x->Right);
}
else {
/* single rotate */
setblack(x);
x = x->Parent;
left_rotate( x->Parent );
}
}
}
}
/* Colour the root black */
setblack(*treeroot);
}
Node* freetree_locate(int size) {
Node* best = NULL;
Node* current = *treeroot;
//Find the smallest (with respect to tree-order) element
//for which size <= Size(current), assuming that size-
//insufficiency is preserved by the tree-order.
while(current != NULL) {
if (size <= Size(current)) {
best = current;
current = current->Left;
}
else
current = current->Right;
}
return best;
}
int freetree_locatemax()
{
Node* n = *treeroot;
if (n == NULL)
return 0;
else
{
while (n->Right)
n = n->Right;
}
return Size(n);
}
void left_child_is2x(Node* x);
void right_child_is2x(Node* x);
//left child is a double-black node. Fix it.
void left_child_is2x(Node* x){
Node* sis = x->Right;
if (sis->Color == RED)
{
left_rotate(x);
x->Color = !(x->Color);
sis->Color = !(sis->Color);
sis = x->Right;
}
//Now sis is black. Let's check its children.
if (isBlack(sis->Right) && isBlack(sis->Left))
{
sis->Color = RED;
if (x->Color == RED)
{
x->Color = BLACK;
return; //done!
}
else
{
//move violation up to parent, if any.
//if node is root, it's already black, and we're done.
if (x->Parent != NULL)
{
if (x->Parent->Left == x)
left_child_is2x(x->Parent);
else
right_child_is2x(x->Parent);
}
return;
}
}
if (isBlack(sis->Right)) //farther child is black
{
//make it so that the farther child is red
right_rotate(sis);
sis->Color = RED; //used to be black, old sis
sis = x->Right;
sis->Color = BLACK; //used to be red. New sis
}
//now we know that sis->Right is red. This is fixable.
left_rotate(x);
sis->Color = x->Color; //just to copy.
x->Color = BLACK; //was indeterminate.
sis->Right->Color = BLACK; //was red.
return;
}
void right_child_is2x(Node* x){
Node* sis = x->Left;
if (sis->Color == RED)
{
right_rotate(x);
x->Color = !(x->Color);
sis->Color = !(sis->Color);
sis = x->Left;
}
//Now sis is black. Let's check its children.
if (isBlack(sis->Right) && isBlack(sis->Left))
{
sis->Color = RED;
if (x->Color == RED)
{
x->Color = BLACK;
return; //done!
}
else
{
//move violation up to parent, if any.
//if node is root, it's already black, and we're done.
if (x->Parent != NULL)
{
if (x->Parent->Left == x)
left_child_is2x(x->Parent);
else
right_child_is2x(x->Parent);
}
return;
}
}
if (isBlack(sis->Left)) //farther child is black
{
//make it so that the farther child is red
left_rotate(sis);
sis->Color = RED; //used to be black, old sis
sis = x->Left;
sis->Color = BLACK; //used to be red. New sis
}
//now we know that sis->Left is red. This is fixable.
right_rotate(x);
sis->Color = x->Color; //just to copy.
x->Color = BLACK; //was indeterminate.
sis->Left->Color = BLACK; //was red.
return;
}
void freetree_delete( Node* z ) {
/*****************************
* delete node z from tree *
*****************************/
if (z == NULL)
return;
if ((z->Left == NULL || z->Right == NULL) && z->Color == RED)
{
Node* child = z->Left ? z->Left : z->Right; //is black
if (child)
child->Parent = z->Parent;
if (z->Parent == NULL)
*treeroot = child;
else if (z->Parent->Left == z)
z->Parent->Left = child;
else
z->Parent->Right = child;
return;
}
else if ((z->Left == NULL || z->Right == NULL) && z->Color == BLACK)
{
Node* child = z->Left ? z->Left : z->Right;
if (child)
child->Parent = z->Parent;
if (z->Parent == NULL)
{
*treeroot = child;
setblack(child);
return;
}
else if (z->Parent->Left == z)
{
z->Parent->Left = child;
left_child_is2x(z->Parent);
return;
}
else
{
z->Parent->Right = child;
right_child_is2x(z->Parent);
return;
}
}
else if (z->Right->Left == NULL && z->Right->Color == RED)
{
//We know that z->Left is non-null
z->Right->Left = z->Left;
z->Left->Parent = z->Right;
z->Right->Parent = z->Parent;
z->Right->Color = BLACK;
if (z->Parent == NULL)
*treeroot = z->Right;
else if (z->Parent->Left == z)
z->Parent->Left = z->Right;
else
z->Parent->Right = z->Right;
return;
}
else if (z->Right->Left == NULL && z->Right->Color == BLACK)
{
z->Right->Left = z->Left;
z->Left->Parent = z->Right;
z->Right->Parent = z->Parent;
z->Right->Color = z->Color;
if (z->Parent == NULL)
*treeroot = z->Right;
else if (z->Parent->Left == z)
z->Parent->Left = z->Right;
else
z->Parent->Right = z->Right;
right_child_is2x(z->Right);
return;
}
else
{
Node* y = z->Right->Left;
Node y2;
while (y->Left)
y = y->Left;
y2 = *y;
*y = *z;
if (z->Parent == NULL)
*treeroot = y;
else if (z->Parent->Left == z)
z->Parent->Left = y;
else
z->Parent->Right = y;
y->Left->Parent = y;
y->Right->Parent = y;
//now y has replaced z. Clean up y2, where y used to be.
y2.Parent->Left = y2.Right;
if (y2.Right)
y2.Right->Parent = y2.Parent;
if (y2.Color == RED)
return;
else
{
left_child_is2x(y2.Parent);
return;
}
}
}
/*
* Invariant: ptr points to a block that is free.
* Afterwards, the pointer will be deleted from
* the relevant data structures, but its tags
* will not reflect the change.
*/
void delFromWherever (void *ptr)
{
if (IsInTree(ptr))
freetree_delete(ptr);
else if (IsInBlob(ptr))
{
//The node is in the blob. Remove it in O(1) time.
List* L = ptr;
if (L->Next != NULL)
L->Next->Prev = L->Prev;
if (L->Prev)
L->Prev->Next = L->Next;
else
*blobroot = L->Next;
}
}
//Takes a block, sets its tags, and puts it in the blob
void queueNewFreeBlock(void* ptr, int size)
{
//Mark and insert into the blob.
List* L = ptr;
setTags(ptr, size, IN_BLOB);
L->Prev = NULL;
L->Next = *blobroot;
if (L->Next)
L->Next->Prev = L;
*blobroot = L;
}
//takes all items from the blob and inserts into the freetree
void emptyblob()
{
/* Move all blob-blocks into the tree */
List* N = *blobroot;
while (N!=NULL)
{
List* temp = N->Next;
freetree_insert(N,Size(N));
N = temp;
}
*blobroot = NULL;
}
int mm_init(void)
{
// 获取初始所需的内存空间
if (mem_sbrk(HEAP_INITSIZE) == (void *)-1)
return -1; // 如果申请失败,返回-1
// 初始化时,空闲块树中有一个自由块
*treeroot = NULL;
// 内存块链表(Blob)最初为空
*blobroot = NULL;
// 设置边界标签,防止内存块与堆边界合并
*boundtag_lo = 0;
*boundtag_hi = 0;
// 初始化块分割的奇偶标志
split_parity = 0;
// 将除边界标签外的初始内存区域加入到空闲块队列中,作为一大块可用内存
queueNewFreeBlock(boundtag_lo + 2, HEAP_INITSIZE - 24);
return 0; // 初始化成功,返回0
}
void mm_free (void *ptr)
{
//内存块的合并
int new_size = Size(ptr);
void *new_block = ptr;
void *prev_block;
void *next_block;
if (NextFree(ptr))
{
next_block = NextBlock(ptr);
new_size += Size(next_block);
delFromWherever(next_block);
}
if (PrevFree(ptr))
{
prev_block = PrevBlock(ptr);
new_size += Size(prev_block);
new_block = prev_block;
delFromWherever(prev_block);
}
//加入到空闲内存块的管理队列
queueNewFreeBlock(new_block,new_size);
}
void *mm_realloc(void *ptr, size_t size)
{
void* next_block = NextBlock(ptr); // 获取当前块的下一个内存块
void* ptr2;
// 如果ptr为NULL,表现如malloc,分配指定大小的内存
if (ptr == NULL) {
return mm_malloc(size);
}
// 如果请求的大小为0,释放原内存并返回NULL
if (size == 0) {
mm_free(ptr);
return NULL;
}
// 如果当前块大小足够,直接返回原指针
if (Size(ptr) >= (size + 8)) {
return ptr;
}
// 检查是否能通过合并下一个空闲块满足新大小需求
if (IsFree(next_block) && (Size(next_block) + Size(ptr) >= size + 8)) {
size = Size(next_block) + Size(ptr); // 合并两个块的大小
delFromWherever(next_block); // 从管理结构中移除下一个块
setTags(ptr, size, 0); // 更新当前块的大小和状态标签
return ptr;
}
// 尝试扩展到下一个块之后,如果它是最后一个块
if (IsFree(next_block) && (NextBlock(next_block) > (char *)boundtag_hi)) {
delFromWherever(next_block); // 移除下一个空闲块的记录
setTags(ptr, Size(ptr) + Size(next_block), 0); // 扩展当前块的大小
next_block = NextBlock(next_block); // 移动到原本的下一个块之后的位置
}
// 如果当前块已是最后一块
if (next_block > (void *)boundtag_hi) {
// 计算最小扩展大小并留出额外增长空间
int min_size = ((size + 15) & -8) + 8;
int grow_size = min_size - Size(ptr) + REALLOC_GROWSIZE;
grow_size &= REALLOC_GROWSIZE; // 确保增长量符合特定对齐或策略
// 尝试扩展内存堆
if (mem_sbrk(grow_size) == (void *)-1) {
// 扩展失败,返回NULL
return NULL;
}
*boundtag_hi = 0; // 更新边界标记
setTags(ptr, Size(ptr) + grow_size, ALLOCATED); // 更新块的大小和状态
return ptr;
}
// 如果上述条件都不满足,需要分配新块并复制数据
ptr2 = mm_malloc(size);
memcpy(ptr2, ptr, Size(ptr) - 8); // 复制数据(注意:实际拷贝大小可能需调整)
mm_free(ptr); // 释放原内存
#if _DEBUG_
// 调试输出
printf("REALLOC: 原内存位于%p,新内存位于%p...\n", ptr, ptr2);
printAllBlocks(); // 打印所有内存块信息
#endif //_DEBUG_
return ptr2; // 返回新分配的内存地址
}
void *mm_malloc (size_t size)
{
void* block; // 指向分配的内存块
int block_size; // 分配块的总大小
void* leftover_block = NULL; // 用于存储分割后剩余的内存块
int leftover_size; // 剩余内存块的大小
size = (size+7)&-8; //将请求的大小调整为8字节对齐,并加上8字节用于内部管理标签
size += 8;
// 确保请求的大小至少为24字节
if (size < 24)
size = 24;
//printf("malloc(0x%x)\n",size);
// 清空或重置内存管理中的某个数据结构(blob)
emptyblob();
// 在空闲块树中查找适合分配的块
block = freetree_locate(size);
// 如果未找到合适的块
if (block == NULL)
{
// 扩展堆内存
block = (char *)mem_heap_hi()+1; // 新块起始位置
block_size = size; // 请求的大小
block_size += HEAP_GROWSIZE; // 额外增加预设的堆增长量
block_size &= -HEAP_GROWSIZE; // 确保增长后的大小满足特定对齐要求
// 尝试通过系统调用增加堆内存
if (mem_sbrk(block_size) == (void *)-1)
{
// 内存不足,无法满足请求
return NULL;
}
*boundtag_hi = 0; //*((int*)(mem_heap_hi()-3)) = NULL; // 更新堆的上界标记
// 如果新块前面的块也是空闲的,则合并这两个块
if (IsFree(PrevBlock(block)))
{
block = PrevBlock(block);
block_size += Size(block);
delFromWherever(block); // 从管理结构中移除已合并的前一块
}
}
else // 找到了合适的空闲块
{
block_size = Size(block); // 获取找到的块的大小
delFromWherever(block); // 从空闲列表中移除这个块
}
// 如果分配的块比实际需要的大很多(超过24字节),考虑分割
if ((block_size - size)>24)
{
// 根据split_parity策略决定分割方向
if (split_parity)
{
leftover_size = block_size - size;
block_size = size; // 分割后当前块的大小
leftover_block = (char *)block + block_size; // 分割后剩余部分的地址
queueNewFreeBlock(leftover_block,leftover_size); // 剩余部分重新加入空闲块管理
}
else
{
leftover_size = block_size - size;
block_size = size;
leftover_block = (char *)block + leftover_size;
queueNewFreeBlock(block,leftover_size); // 当前块成为剩余部分,重新加入管理
block = leftover_block; // 更新block为实际分配的块地址
}
split_parity = !split_parity; // 切换下一次分割的方向
}
// 设置块的大小和分配状态标签
setTags(block,block_size,0);
//printBlock(block);
// 调试代码:打印堆信息、所有块信息及红黑树验证
#if _DEBUG_
if (printHeap());
printAllBlocks();
isRedBlack();
#endif //_DEBUG_
// 返回分配的内存块地址
return block;
}