按照上篇文章的思路,自己实现的一个简单malloc:
#include <unistd.h>
#include <stdio.h>
struct header_t {
size_t size; //8B
unsigned is_free; //4B
struct header_t* next; //8B
};
struct header_t* g_head = NULL;
struct header_t* g_tail = NULL;
struct header_t* get_free_block(size_t size) {
struct header_t* curr = g_head;
while(curr) {
if (curr->is_free && curr->size >= size) {
return curr;
}
curr = curr->next;
}
return NULL;
}
void* malloc(size_t size) {
if (!size) {
return NULL;
}
struct header_t* header = get_free_block(size);
if (header) {
header->is_free = 0;
return (void*)(header + 1);
}
size_t total_size = sizeof(struct header_t) + size;
void* block = sbrk(total_size);
if (block == (void*)-1) {
return NULL;
}
header = block;
header->size = size;
header->is_free = 0;
header->next = NULL;
if (g_head == NULL) {
g_head = header;
}
if (g_tail) {
g_tail->next = header;
}
g_tail = header;
return (void*)(header + 1);
}
void free(void* block) {
if (!block) {
return;
}
struct header_t* header = (struct header_t*)block - 1;
void* programbreak = sbrk(0);
//如果要释放的模块位于链表中的最后一个,则在链表中删除最后一个内存块,并释放内存,否者该内存块只是标记一下为free
if ((char*)block + header->size == programbreak) { //block is the last one in the list
if (g_head == g_tail) { //only one block in the list
g_head = g_tail = NULL;
}
else {
struct header_t* tmp = g_head;
while(tmp) {
if (tmp->next == g_tail) {
tmp->next = NULL;
g_tail = tmp;
}
tmp = tmp->next;
}
}
sbrk(0 - header->size - sizeof(struct header_t));
}
else {
header->is_free = 1;
}
}
void* realloc(void* block, size_t size) {
if (!block || !size) {
return malloc(size);
}
struct header_t* header = (struct header_t*)block - 1;
if (header->size >= size) {
return block;
}
void* ret = malloc(size);
if (ret) {
memccpy(ret, block, header->size);
free(block);
}
return ret;
}
int main()
{
char* p = (char*)malloc(1);
printf("p is %p\n", p);
p = (char*)malloc(2);
printf("p is %p\n", p);
return 0;
}
代码还算清晰简单,总体的思路是链式内存管理法。分配内存时,先看链表中有没有空闲且足够大小的内存块。没有的话则分配需要大小的内存,加上用来管理内存结点的内存,然后加入到链表中。链表中有空闲的内存块是因为,后面调用free释放内存时,不会里面释放内存,只是加了一个标志位,保存起来供后面的分配使用。
我们注意到,分配内存的时候有所需大小这个参数,为什么释放的时候没有呢?难道不需要知道释放内存的大小吗。如果释放的时候也要知道内存的大小,对开发者而言显得就那么不太友好,因为还要保存分配内存的大小。幸好聪明的开创者门已为我们找到了解决方案,利用内存块管理节点来保存,上面的代码中也是这么做的。
以上的代码只是基本实现了malloc和free函数,但是效率上肯定是不能和glibc的相比的。上面的代码也有很多改进的地方,例如单链表查找的方式,效率过于低下。适配空闲内存块的算法也有很多,比如最先适配,最好适配等等。还有多线程也不支持,有机会再来剖析glibc的malloc即ptmalloc分配算法。
参考:
https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/