前言:
本人做了一段时间的linux c开发,一直都在用malloc/free等内存操作,但是没有对其深入学习.
最近有时间才去研究.
在网上找到一篇叫<Malloc_tutorial.pdf>的文献,里面详细的讲了linux下malloc等内存操作的底层实现.
相关学习资料:
英文版链接Malloc_tutorial.pdf
代码:
以下代码根据文献实现的,简单实现了的malloc/calloc/free操作.
#include <stdio.h>
#include <unistd.h>
// 操作系统为64为操作系统,指针为8字节,size_t为8字节,int为4字节
// 内存按照8字节长度对齐,分配的内存必须为8的整数倍
#define align8(x) (((((x)-1)>>3)<<3)+8)
#define BLOCK_SIZE 40 //s_block 除了数据首地址data的结构体大小
typedef struct s_block* t_block;
// s_block结构体是用来描述分配的内存信息
struct s_block
{
size_t size; //数据域data长度, 8字节
t_block next; //指向下一个 s_block, 8字节
t_block prev;// 指向前一个s_block, 8 字节
int free; // 标志是否空闲, 1表示空闲; 0表示不空闲 , 4字节
int padding; //填充4字节,保证meta块长度为8的倍数
void* ptr; // 存储data地址,用于验证有效性, 8字节
// 数据首地址
char data[1] ;//这是一个虚拟字段,表示数据块的第一个字节,长度不应计入meta
};
//base是一个全局指针变量,指向堆的开始位置
t_block base = NULL;
/**
* 在链表中查找符合申请size 的空闲free的内存,如果没有查到返回NULL,
* last指向最后一块内存查到的内存;
*/
t_block find_block(t_block* last, size_t size)
{
t_block b = base;
while (b && !(b->free && b->size >= size))
{
*last = b;
b = b->next;
}
return b;
}
/**
* 扩展内存
*
*/
t_block extend_head(t_block last, size_t size)
{
t_block b;
b = sbrk(0); // 获取当前break指针的位置
if ((void*)-1 == sbrk(BLOCK_SIZE + size))
{
// sbrk 分配失败
return NULL;
}
// sbrk 分配成功
b->size = size;
b->next = NULL;
b->prev = last;
if ( last ) //last不为空,说明链表不为空
{
last->next = b;
}
b->ptr = b->data;
b->free = 0;
return b;
}
/**
* 分裂内存
* 当空闲内存大小在分配完s长度后剩下的空间超过BLOCK_SIZE+8,
* 则将剩余的内存分裂出来
*/
void split_block(t_block b, size_t s)
{
if (!b) // 无效的空闲块
{
return ;
}
t_block new;
new = b->data + s; //新内存 指针指向分配完BLOCK_SIZE+8剩余的空闲内存
new->size = b->size - s -BLOCK_SIZE;// 存储剩余空间大小
new->free = 1; // 新内存设置为空闲内存
new->next = b->next; //
b->next->prev = new;
new->prev = b;
b->size = s;
b->next = new;
}
/**
* 合并内存
*/
t_block fusion(t_block b)
{
if (b->next && b->next->free){
b->size += BLOCK_SIZE + b->next->free;
b->next = b->next->next ;
if ( b->next)
{
b->next->prev= b;
}
}
return b;
}
/**
* 用于通过数据data地址获取block地址
*
*/
t_block get_block(void* p)
{
char* tmp;
tmp = p;
tmp -= BLOCK_SIZE;
p = tmp;
return p ;
}
int valid_addr(void* p)
{
if (base)
{
if ( p > base && p < sbrk(0))
{
return p == (get_block(p)->ptr);
}
}
return 0;
}
/**
* 释放内存
*
*/
void free(void* ptr)
{
t_block b;
if(valid_addr(ptr))
{
b = get_block(ptr);
b->free = 1;
if (b->prev && b->prev->free) // b上一个模块是空闲的
{
b = fusion(b->prev); // 如果符合条件,合并前一个模块
}
if (b->next) // b有后一个模块
{
fusion(b);// 如果符合条件,合并后一个模块
}
else // 在b后面的位置没有模块,
{
if (b->prev)
{
b->prev->next = NULL; // 将b前一个模块指向的后一个模块的指针设置为NULL
}
else
{
base = NULL;
}
brk(b);
printf("free b=%p\n", b);
}
}
}
void* malloc(size_t size)
{
t_block last, b;
size_t align_size = align8(size);//将申请的字节按照8字节对齐
if ( base ) // 链表里存在内存块,
{
last = base;
if ( (b = find_block(&last, align_size)) ) // 查找是否有空闲的内存可用
{
if (b->size - align_size >= (BLOCK_SIZE + 8)) // 判断空闲的内存在分配完后剩余的内存是否够存放一个指针指向分配完BLOCK_SIZE+8
{
split_block(b, align_size); // 分裂空闲内存
}
b->free = 0;
}
else // 没有符合条件的空闲内存, 申请一块新的内存
{
b = extend_head(base, align_size);
if (!b)
{
return NULL;
}
}
}
else // 首次调用malloc函数, 申请一块新的内存
{
b = extend_head(base, align_size);
if (!b)
{
return NULL;
}
base = b;
}
return b->data;
}
void* calloc(size_t number, size_t size)
{
size_t * new;
size_t s8, i;
new = malloc ( number * size);
if ( new )
{
s8 = align8(number*size);//>> 3;
printf("calloc align8=%d\n", s8);
for (i=0; i< s8; i++)
{
new[i] = 0;// new为size_t,所以这里+1的步长为size_t的字节数,在64位整型下面,size_t为8字节
}
}
return new;
}