简易malloc和free函数实现

Aki 发布于 2023-01-28 239 次阅读


在C语言中只能通过malloc()和其派生的函数进行动态的申请内存,而实现的根本是通过系统调用实现的(在linux下是通过sbrk()系统调用实现)。

malloc()到底从哪里得到了内存空间?答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

malloc()在运行期动态分配分配内存,free()释放由其分配的内存。malloc()在分配用户传入的大小的时候,还分配的一个相关的用于管理的额外内存,不过,用户是看不到的。所以,实际的大小 = 管理空间 + 用户空间。

重要函数sbrk和brk、

void* sbrk(intptr_t incr);

函数作用是:将内核的brk指针增加incr来扩展和收缩堆。实际上malloc的实现调用了这个关键函数。函数调用成功则返回旧的sbrk指针。函数调用失败则返回(void*)-1。

如下所示申请了16个字节返回的是old的指针!!!!!

int brk(void *addr);

函数的作用就是设置brk指针指向的地址,也就是heap分配内存开始的地址,用来free回收内存!!!成功返回0,失败返回-1。

实现、

在linux系统下面一个程序的堆的管理是通过内存块进行管理的,也就是将堆分成了很多大小不一的内存块。这些块怎么管理呢,比如怎么查询块的大小,怎么查询块是否正在被程序使用,怎么知道这个块的地址。为了解决内存块的管理所以要设计一个管理内存块的数据结构,详细的数据结构如下:

//内存块结构体设计,设计一个双向链表来管理
class Memory_Block
{
public:
	Memory_Block* _next = nullptr;  //前一个内存块指针
	Memory_Block* _prev = nullptr;  //后一个内存块指针
	size_t size = 0;                //内存块分配的可用的size,也就是用户使用的size
	bool  is_allocated = false;     //标记内存块是否可用
};

有了管理内存块的数据结构,那么在内存中堆的组织形式也好理解了,也就是堆是由很多内存块组成的,所以有了如下的示意图:

综合上面的知识,可以很容易想到malloc()实现的大体思路。首先挨个检查堆中的内存块是否可用,如果可用那么大小是否能满足需求,要是都满足的话就直接使用。当遍历了堆中的所有内存块时,要是没有能满足需求的块时就只能通过系统调用向操作系统申请新的内存,然后将新的内存添加到堆中。

看完上面的思路,也会很容易的想到free()函数的实现思路,只要将内存管理块设置为可用就可以了。这样下次调用malloc()函数的时候就可以将该内存块作为可分配块再次进行分配了。然后从最后一块内存块开始往前遍历,如果该内存块不为空且是没有被使用的块,就调整堆顶指针,释放掉该块。

全部实现如下、

#include<iostream>
using namespace std;
#include<mutex>
#include<unistd.h>



//内存块结构体设计
class Memory_Block
{
public:
	Memory_Block* _next = nullptr;
	Memory_Block* _prev = nullptr;
	size_t size = 0;
	bool  is_allocated = false;
};


//记录内存块大小,32个字节
const size_t Memory_Block_Size = sizeof(Memory_Block);


//记录堆顶部和底部
void* heap_top = nullptr;
void* heap_start = nullptr;



//线程安全的打印函数
mutex print_mutex{};
template<class...Args>
void print(const Args&...args)
{
	lock_guard<mutex>m{print_mutex};
	((cout<<args),...);
}


//输出错误信息,并退出程序
void unix_error(const char* msg)
{
	print(msg);
	exit(0);
}



//申请内存函数
mutex malloc_mutex{};
void* my_malloc(size_t size)
{
	//上锁,为了线程安全
	lock_guard<mutex> m{malloc_mutex};

	//查找之前分配的内存块中有没有适合size的块
	Memory_Block* start = static_cast<Memory_Block*>(heap_start);
	for(;start != nullptr;start = start->_next)
	{
		if(!start->is_allocated && start->size >= size)
		{
			break;
		}
	}

	//没有内存合适的块就需要申请
	if(start == nullptr)
	{
		void* ptr = sbrk(size + Memory_Block_Size);
		if(ptr == (void*)(-1))
		{
			unix_error("malloc error\n");
		}
		start = static_cast<Memory_Block*>(ptr);
		start->size = size; 
		Memory_Block* top = static_cast<Memory_Block*>(heap_top);
		if(top == nullptr)
		{
			start->_prev = start->_next = nullptr;
			heap_start = ptr;
		}
		else
		{
			start->_prev = top;
			start->_next = nullptr;
			top->_next = start;
		}

	        print("malloc ",start->size," bytes memory\n");
		heap_top = ptr;
	}
	start->is_allocated = true;
	return static_cast<void*>(start + 1);

}



//释放内存函数
mutex free_mutex{};
void my_free(void* ptr)
{
	lock_guard<mutex> m{free_mutex};
	if(ptr == nullptr)
	{
		return;
	}

	Memory_Block* p = static_cast<Memory_Block*>(ptr);
	p = p - 1;
	p->is_allocated = false;

	Memory_Block* end = static_cast<Memory_Block*>(heap_top);

	//释放内存,从最后一个块开始往前释放
	for(;end != nullptr && end->is_allocated == false;end = static_cast<Memory_Block*>(heap_top))
	{
		heap_top = end->_prev;
		if(end->_prev)
		{
			end->_prev->_next = nullptr;
		}
		print("free ",end->size," bytes memory\n");
		if(brk(end) < 0)
		{
			unix_error("free error\n");
		}
	}

	if(heap_top == nullptr)
	{
		heap_start = nullptr;
	}
}



//检查内存泄漏函数
void check_memory_leak()
{
	Memory_Block* start = static_cast<Memory_Block*>(heap_start);
	void* end = sbrk(0);
	for(;start && start < end;start = start->_next)
	{
		print("memory leak ",start->size," bytes\n");
	}
}

		
	       


int main()
{

	print("pragram start,heap top = ",sbrk(0),"\n");


	int* array[20];
	for(int i = 0;i<20;++i)
	{
		array[i] = static_cast<int*>(my_malloc(i));
	}


	for(int i = 10;i<20;++i)
	{
		my_free(array[i]);
	}

	check_memory_leak();

	print("pragram end, heap top = ",sbrk(0),"\n");
	return 0;
}