我们知道,在程序中进行内存分配和处理,是一个合格码农必备的技能。高级语言都通过GC实现内存的管理,减轻了程序员的自己撰写内存管理相关代码的负载。但是使用GC的语言本身很难实现高效性能,在需要高性能计算能场合下C语言还是一个必要的选择,尤其Linux下很多关键的底层类库都是用C撰写的高级语言对其类库进行打包使用。
在构建内存分配器之前,首先我们需要熟悉下程序的内存布局。在现实中,用户层都是不能直接接触到物理地址的内存,而是通过程序进程中,每一个进程OS会给其分配一个虚拟地址空间内,该虚拟地址空间与其他进程的虚拟地址空间不同。每个进程虚拟地址空间通常包含5个部分:文本部分:这部分是处理器执行的二进制指令的部分。数据部分:非零初始化静态数据。BSS符号启动块:存放零初始化的静态数据。
堆栈和堆在相反的方向上做数据增长。有些文档中把数据,bss和堆部分统称为数据段,数据端末尾由名为programbreak或brk的指针划分,brk指向堆的末尾。如果我们想在堆中分配更多内存,需要请求系统增加brk。对应地如果我们要释放内存,需要请求系统减少brk。在Linux(或类Unix系统)中,我们可以通过sbrk()系统调用操作程序中断,调用sbrk(0)就会给出程序中断的当前地址。
DIY编程实现自己的内存分配器
我们首先检查请求的大小size是否为零。如果是,那么我们返回NULL。如果大小参数非零,则通过pthread_mutex_lock(&global_malloc_lock)获得锁定。接着调用get_free_block()得到标志头header;紧接着是遍历链表,看看是否存在一个标记为空闲的内存块,并且可以容纳给定的大小。
首先我们要释放的标志头。我们需要做的就是获得一个位于块后面的指针,大小为头大小的内存块。因此,我们将块转换为标志头指针类型并将其移动1个单位。header=(structheader_t*)block-1;sbrk(0)给出程序中断的当前值。为了检查要释放的块是否在堆的末尾,我们首先找到当前块的结尾地址:(char*)block+header->size。然后将其与程序中断做比较。
在这里,我们快速检查乘法溢出,然后调用malloc(),并使用memset()将分配的内存清除为全零。reallocrealloc()将给定内存块的大小更修改为指定的大小。
领取专属 10元无门槛券
私享最新 技术干货