malloc实现(c)

本文介绍了使用C语言实现malloc和free函数,通过sbrk系统调用管理内存。讨论了内存布局,特别是堆区的扩展,以及内存对齐的概念。文章还详细讲解了隐式和显示空闲链表的数据结构及其在内存分配中的作用,并给出了部分接口的实现代码。
摘要由CSDN通过智能技术生成

前言

使用c语言实现malloc,加深对内存分配的理解。主要实现两个内存分配相关的函数,malloc与free

前置知识

内存布局相关

malloc分配的内存是虚拟内存,首先了解虚拟地址空间。虚拟地址空间内部分为内核空间用户空间。32位系统的内存布局如下图所示
用户空间分布情况
其中堆区文件映射区的内存是动态分配的,分别对应系统调用sbrk()、brk()以及mmap()malloc()在堆区还是文件映射区分配内存取决于要分配的内存大小,本文主要使用系统调用sbrk()

C语言相关

sbrk()

brk()sbrk()都是扩展堆区的上界break,从而获得新的内存空间,本实验主要使用sbrk()

函数原型:void *sbrk(sizt_t size);
说明:使用该函数将堆区空间增加size字节,分配成功返回堆区上界break

struct

使用结构体定义双向链表

typedef struct _Node {
	size_t size;				//可使用大小
	struct _Node *prev;			
	struct _node *next;			
} Node;

结构体变量初始化:Node *node = NULL;,初始化为NULL后,并没有为node分配空间,不指向内存中的任何地址

访问结构体变量的两种方式:①node->size(*node).size

取结构体的地址进行运算:(char *)将地址转化为字节长度进行运算,使用(void *)编译器可能报错

内存对齐

CPU访问内存不是逐个字节访问,而是以字长(word size)为单位
举个例子:32位CPU,一个int变量占4字节,假设存放的地址为0x 00000001 ~ 0x 00000005。为了取到该变量,CPU要进行两次寻址,0 ~ 3与4 ~ 7,然后再拼接处理,影响了访问的性能。若内存对齐,则能够减少访问次数。

结构体内存对齐规则:

  1. 第一个成员在结构体地址偏移量为0的地方
  2. 其它成员变量的偏移量 = 该变量对齐数的整倍数(对齐数取编译器默认值(#pragma pack())与该变量所占内存大小中的较小值)
  3. 结构体的大小 = 所有成员最大对齐数的整倍数
  4. 数组以数组本身的类型计算,如 int a[5] 以 int 类型大小计算对齐

举个简单的例子:假设 #pragma pack(4),表示编译器默认对齐数为4字节

struct Test {
	char a;				
	char b;			
	int c;			
};

此时变量的偏移量分别为0、1、4,结构体总大小为8字节,若定义顺序改变,如下

struct Test {
	char a;				
	int b;			
	char c;			
};

此时变量的偏移量分别为0、4、8,结构体总大小为12字节。所以在设计数据结构时,要合理的定义变量。

具体实现

数据结构

隐式空闲链表

特点:结构体成员中包含free标志,用于判断该内存块是否是空闲可用的。
缺点:分配内存时,需要沿着链表一直遍历到合适的内存块,无差别遍历,会遍历已分配出去的内存块,性能低。

显示空闲链表

链表中只包含空闲的内存块,调用malloc时只遍历空闲的内存块,效率高。当执行free(*ptr)操作时,将释放的内存块加入到链表当中,并且看能不能和相邻的空闲块合并,组成更大的内存块。因为要访问相邻的链表,故考虑使用双向链表。

部分接口实现

void *my_malloc(size_t size, int type) {
    Node *cur = freeHead;

    cur = type == 1 ? getFirstFitAdd(cur, size) : getBestFitAdd(cur, size);

    if (cur != NULL){
        split(cur, size);
        return (char *)cur + nodeSize;
    } else{
        Node *node = (Node *)sbrk(size + nodeSize);     //用户可用区+元数据区
        node->prev = NULL;
        node->next = NULL;
        node->size = size;
        return (char *)node + nodeSize;
    }
}

说明:size为分配的内存大小(可使用内存,不包括元数据区);type为分配的策略

sbrk()申请的大小应为size + sizeof(Node)(用户可使用内存+元数据所占内存)

两种分配策略:
first fit:返回第一个满足所需大小的内存块
best fit:将链表遍历完,返回分配后残留空间最小的内存块

void *getFirstFitAdd(Node *cur, size_t size) {
    Node *node = cur;
    while (node != NULL) {
        if (node->size >= size) {
            return (char *)node + nodeSize;
        }
        node = node->next;
    }
    return NULL;
}

说明:从cur开始搜索,返回第一个大小大于size的内存块的地址
同理可得getBestFitAdd(),这里省略不写了

void split(Node *cur, size_t size) {
    //enough
    if (cur->size >= size + nodeSize){
        Node *split_block = (Node *)((char *)cur + size + nodeSize);    //怎么表示结构体地址??
        split_block->size = cur->size - size - nodeSize;
        split_block->next = NULL;
        split_block->prev = NULL;
        addIntoLinkedList(split_block);

        cur->size = size;
        removeFromLinkedList(cur);
    }else {
        removeFromLinkedList(cur);
    }
}

说明:申请内存成功后,进行内存分割,将所申请的内存块中多余的内存分割出来。如果当前传入的块内存空间大于size + sizeof(node),则证明可以进行分割;若不能,直接移除该结点。

void ff_free(void *ptr) {

    Node *cur = (Node *)((char *)ptr - nodeSize);
    addIntoLinkedList(cur);

    mergeBackIfPossible(cur);
    mergeFrontIfPossible(cur);
}

说明:释放内存,将释放的内存加入到链表当中来,加入后记得合并相邻空间(如果可以的话)。

void mergeFrontIfPossible(Node *cur) {
    if (cur->prev != NULL && ((char *)cur->prev + cur->prev->size + nodeSize) == (char *)cur) {
        cur->prev->size += cur->size + nodeSize;
        removeFromLinkedList(cur);
    }
}

说明:若该内存块能与前一个内存块合并,则合并,并删除原有块
mergeBackIfPossible()与之类似,不写了

结尾

新手,一些细节可能写错,望指正

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值