本文共 2069 字,大约阅读时间需要 6 分钟。
一、关于malloc
很早就想写这篇文章了,一直拖着没有写,是想写个更完善的版本,不过最近确实没有太多时间去考虑一个很完整的版本,只有先把这个简单的版本写出来了。据说在微软今年招实习生的时候面过这个题目。
malloc()是C语言中动态存储管理的一组标准库函数之一,其作用就是从内存的动态存储区(堆)中分配一个长度为size的连续内存空间。参数为一个无符号的整数,返回值则是指向所分配的连续存储空间起始地址的指针。如果内存不足,则函数分配内存失败会返回NULL。
在Linux中,每个进程可访问的虚拟内存空间为3G,但在程序编译时,不可能也没必要为程序分配这么大的空间,只分配并不大的数据段空间,程序中动态分配的空间就是从这一块分配的。如果这块空间不够,malloc函数族(realloc,calloc等)就调用sbrk函数将数据段的尾部移动,sbrk函数在内核的管理下将虚拟地址空间映射到内存,供malloc函数使用。注意,sbrk不是系统调用,是C库函数。系统调用通常提供一种最小功能,而库函数通常提供比较复杂的功能。有关sbrk详细内容可参见这篇文章: 。如果待分配的内存很大,则有可能直接调用mmap来映射内存。这里顺便提一下malloc和calloc的区别,malloc对它所分配的内存是不会清零的,而calloc则会对它所分配的内存清零。
二、动手实现
首先定义一个控制内存分配的结构体内存控制块mcb如下:
#include // 使用brk/sbrk,需要使用这个头文件
- typedef struct mcb {
- int available;
- int size;
- } mcb;
其中available表示该内存块是否可用,1表示可用,0表示不可用。size表示该内存块的大小。 我们在malloc一段内存的时候,会在基本大小的基础上加上结构体mcb的大小,而返回的分配内存地址则需要跳过结构体mcb。 定义几个全局变量如下,memStart表示内存分配时查找空闲内存的起始地址。lastAddr则表示最后一个有效的内存地址,hasInit代表是否已经初始化。初始化函数设置memStart为当前堆结尾位置,而lastAddr也设置为当前堆结尾位置。
- void *memStart;
- void *lastAddr;
- int hasInit;
- void init()
- {
- lastAddr = sbrk(0);
- memStart = lastAddr;
- hasInit = 1;
- }
malloc函数实现如下:
- void* malloc_mem(int num)
- {
- if (!hasInit)
- init();
- void *current = memStart;
- void *ret = NULL;
-
- num += sizeof(mcb);
-
-
-
- while (current != lastAddr) {
- mcb *pcurrent = current;
- if (pcurrent->available && pcurrent->size >= num){
- pcurrent->available = 0;
- ret = current;
- break;
- }
- current += pcurrent->size;
- }
- if (!ret) {
- sbrk(num);
- ret = lastAddr;
- lastAddr += num;
- mcb *pcb = ret;
- pcb->size = num;
- pcb->available = 0;
- }
- ret += sizeof(mcb);
- return ret;
- }
既然写了malloc,则free就很好写了, 因为传入参数为内存块真正的起始地址(跳过了mcb结构体大小),所以这里需要先减去结构体mcb的大小才能得到内存控制块mcb的内存位置 ,而后清除占用标记即可。 - void free_mem(void *start)
- {
- mcb *pmcb = (mcb *)(start - sizeof(mcb));
- pmcb->available = 1;
- }
转载地址:http://rrhxi.baihongyu.com/