写在前面: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中标记为空闲。为了效率在代码中多次使用位运算。
总之阅读这段代码收获还是挺大的。