malloc实现(c)

前言

使用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()与之类似,不写了

结尾

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

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值