如何实现一个简单内存分配
具体代码可以参考我这里实现的简单动态内存分配
这里仅仅只是实现了的内存的分配,之后还需要实现内存池,并且通过栈的方式进行管理才能方便使用。
在实现这个问题之前,我们需要了解一下程序中的内存布局。
- text
存放要由处理器执行的二进制指令的部分。 - data、
存放非零初始化的静态数据 - bss
存放初始化为零的静态数据,并且未初始化的静态数据被初始化为零后将合并到这个区域 - heap
这里主要是动态分配的数据,也叫堆 - stack
这里是局部变量等非动态分配的数据,也叫栈
可以看到堆和栈的增长方式是相对生长
开始有个一个指针叫做program break或者brk,他是与bss重合的,当开始分配的内存,brk会上升,其中增大的内存空间为heap。
所以当我们需要动态分配空间的时候,需要请求系统,系统内部接口会帮助我们增大brk指针
相反,当我们需要释放空间的时候,也需要请求系统,系统内部接口会帮助我们减小brk指针
如何操控brk指针
本文使用的是linux操作系统。系统接口函数为 sbrk()
当然也可以使用 brk() 这个系统调用。
具体使用方法可以通过命令 man sbrk 来查看。
1 | sbrk(0); // 获取当前brk指针 |
申请或者释放total_size个字节空间
1 | void *now_addr = sbrk(total_size); |
由于每次分配内存返回的是其实地址, 考虑到释放问题, 我们每次分配都需要添加一个头结构体, 用来保存一下用户需要分配的内存大小, 以及进行填充来实现内存对齐。
添加头部分
我这里实现的是对齐16个字节
1 | // 16 位对齐 |
其实这里可以化简, 不需要指向下一个以及上一个被分配的空间的指针
1 | // 16 位对齐 |
关于内存对齐问题
我们调用sbrk分配空间的时候, 为了提高Cpu读取的效率, 我们需要进行内存对齐
例如 我们的Cpu每次读取16个字节
如果 分配的内存是16的n倍, 那么只需要读取n次即可
但是如果不是, 那么就会读取超过n次
1 | 假设XXXX为其他数据, OOOO为我们分配的数据 |
这就是内存对齐的好处
1 | // 如果要分配size个字节, 那么实际要分配total_size个字节 |
其实就是使得每次分配的内存空间(包含头以及填充), 使得为16的整数倍
malloc核心代码
1 | // malloc memory space |
free核心代码
1 | // free memory space |