malloc calloc realloc free的简单实现

写在前面:csdn的博客排版就是shit,祝早日关门大吉

内存分配其实是个必修课,应该清楚地知道一个程序在计算机中的内存分布情况,linux程序在内存中的分布情况是这样的:
老子也不知道这图从哪来的

当然啦除了知道诸如“堆从低地址向高地址增长栈从高地址从低地址增长”这种东西之外最好还要知道“什么是分页为什么分页malloc的内部实现用到了brk()sbrk()MMU是什么”之类的这种东西。

切入正题,一个简单的malloc实现如下:

#include <stdio.h>
#include "unistd.h"
#define BLOCK_SIZE 40

typedef struct s_block *t_block;
void *first_block=NULL;

t_block find_block(t_block *last,size_t size);
t_block extend_heap(t_block last, size_t s);
void split_block(t_block b,size_t s);
size_t align8(size_t s);
void *malloc(size_t size);
void *calloc(size_t number, size_t size);
t_block get_block(void *p);
int valid_addr(void *p);
t_block fusion(t_block b);
void free(void *p);
void copy_block(t_block src,t_block dst);
void *realloc(void *p,size_t size);


struct s_block{
    size_t size;//数据区大小
    t_block prev;//指向上个块的指针
    t_block next;//指向下个块的指针

    int free;//判断是否是空闲块

    int padding;//填充4字节,为了迎合结构体对齐,保证meta块长度为8的倍数

    void *ptr;//Magic pointer,指向data

    //虚拟字段这个是真的巧妙!每个块的s_block后面都是数据区,但是这个data[]不算作s_block的内容
    //不计作s_block的长度,所以在访问s_block的data字段时实际上访问的是数据区的第一个字节
    char data[1];
};

int main(void){

    return 0;
}

t_block find_block(t_block *last,size_t size){
    t_block b=first_block;
    while (b && !(b->free && b->size >= size)) {
        *last=b;//last用来表示最后一块可用的内存块(可能是刚刚被释放过之后的一个块)
        b=b->next;
    }
    return b;
}

t_block extend_heap(t_block last, size_t s){
    t_block b;//强制把新申请出来的内存看做是s_block类型的
    b = sbrk(0);
    if (sbrk(BLOCK_SIZE + s) == (void *) -1) {
        return NULL;
    }

    b->size=s;
    b->next=NULL;
    b->ptr = &(b->data);//data是一个数组所以其实不用加&
    if(last){
        last->next=b;
    }
    b->free=0;
    return b;
}

void split_block(t_block b,size_t s){
    t_block new;
    new=b->data+s;
    new->size = b->size - s - BLOCK_SIZE;
    new->next = b->next;
    new->free=1;
    b->size=s;
    b->next = new;
}

size_t align8(size_t s){
    if (s & 0x7 == 0) {//满足该条件的s是可以被8整除的
        return s;
    }

    //这样可以得到一个大于s且能够被8整除的最小的数
    return ((s>>3)+1)<<3;
}

void *malloc(size_t size){
    t_block b,last;
    size_t s = align8(size);//地址对齐
    if (first_block) {
        last=first_block;//
        b = find_block(&last, s);
        if(b){
            if ((b->size) >= (BLOCK_SIZE + 8)) {
                split_block(b, s);
            }
            b->free=0;
        } else {
            b = extend_heap(last, s);
            if (!b) {
                return NULL;
            }
        }
    }else{
        b = extend_heap(NULL, s);
        if(!b) {
            return NULL;
        }
        first_block=b;
    }
    return b->data;
}

void *calloc(size_t number, size_t size){
    size_t *new;
    size_t s8;
    new = malloc(number * size);
    if (new) {
        s8=align8((number*size))>>3;
        for (int i = 0; i < s8; i++) {
            new[i]=0;
        }
    }
    return new;;
}

t_block get_block(void *p){
    char *tmp;
    tmp=p;
    return (p = tmp -= BLOCK_SIZE);
}

int valid_addr(void *p){
    if (first_block) {
        if(p<sbrk(0)&&p>first_block){
            return p==(get_block(p))->ptr;//??????
        }
    }
    return 0;
}


t_block fusion(t_block b){
    if (b->next && b->next->free) {
        b->size+=BLOCK_SIZE+b->next->size;
        b->next = b->next->next;
        if (b->next) {
            b->next->prev=b;
        }
    }
    return b;
}

void free(void *p){//传进来的p实际上是那个block的数据区
    t_block b;
    if(valid_addr(p)){
        b = get_block(p);//b此时指向那块block的起始地址
        b->free=1;//只是标记为空闲,实际上data区还是有数据的
        if (b->prev && b->prev->free) {
            b=fusion(b->prev);
        }
        if (b->next) {//检查当前block是否还有后继,即检查是否是最后一个block
            fusion(b);//如果有则尝试合并
        }else{//如果没有后继则先检查是否是第一个block
            if (b->prev) {//检查是否有前驱,如果有前驱则表示该block不是第一个
                b->prev->next = NULL;
            }else{//如果没有前驱那么该block既是第一个也是最后一个block
                first_block = NULL;//又回到了任何一次malloc之前
            }
            brk(b);//用brk在heap上进行回退
        }
    }
}



void copy_block(t_block src,t_block dst){
    size_t *sdata, *ddata;

    sdata=src->ptr;
    ddata=dst->ptr;
    for (size_t i = 0;(i*8)<src->size&&(i*8)<dst->size; i++) {
        ddata[i] = sdata[i];
    }
}

void *realloc(void *p,size_t size){
    size_t s;
    t_block b, new;
    void *newp;
    if(!p){
        return malloc(size);
    }
    if(valid_addr(p)){
        s = align8(size);
        b = get_block(p);//p是数据区起始,此时b指向那整块内存的起始
        if(b->size>=s){//如果这个块能直接放下原数据大小则对数据什么也不做
            if(b->size-s>=(BLOCK_SIZE+8)){//并顺手检查一下是否可以切割内存
                split_block(b, s);
            }
        }else{//如果放不下则检查一下是否可以合并,允许合并的条件的最后一条是合并之后能放下数据
            if (b->next && b->next->free &&
                    b->next->size + b->size+BLOCK_SIZE >= s) {
                fusion(b);
                //如果可以合并则在合并完之后检查是否可以进行分裂
                if(b->size-s>=(BLOCK_SIZE+8)){
                    split_block(b, s);
                }
            }else{//如果既放不下数据也不符合合并的条件
                //那么就malloc一块新内存
                newp = malloc(s);
                if(!newp){
                    return NULL;
                }
                new = get_block(newp);
                copy_block(b, new);
                free(p);
                return newp;
            }

        }
        return (p);
    }
    return NULL;
}

这段代码不是我的原创,我读过去之后加了一些注释方便理解。引用到的一些代码在这里
http://www.inf.udec.cl/~leo/Malloc_tutorial.pdf
只说说阅读这段代码的感受吧。
底层代码写的真的是滴水不漏啊,看的过程中给我一种很严谨的感觉。
代码中有一些比较取巧的写法,比如结构体里的虚拟成员,为了验证地址是否合法而加的magic pointer,释放内存时不是对内存进行覆盖而是在开头的指示块的free中标记为空闲。为了效率在代码中多次使用位运算。
总之阅读这段代码收获还是挺大的。

猜你喜欢

转载自blog.csdn.net/u010742342/article/details/72663562