最近做项目需要用到动态内存分配,在了解到使用C库的malloc和free的时候,会产生内存碎片,而单片机的内存是很宝贵的,所以萌生了自己写一个分配算法的想法,在这里我并没有使用链表来做这个,是因为我觉得没有位图好操作。
什么是位图?
位图 (Bitmap),也称为位数组 (Bit Array)或位集合(Bit Set),是一种使用比特位(bit)来表示数据的数据结构。每个比特位只能为0或1,用来表示某个元素是否存在或某种状态的布尔值。
位图用一个比特位表示数据是否存在,正好可以表示内存块是否被使用,而且位图占用空间小,易于操作,对指定位进行清除以后,巧合的是正好可以达到合并内存块的效果,可谓一举两得,再也不会像链表那样,释放内存后还需手动合并空闲内存块。
这里我设计的思路是,位图的每一个位表示一个8字节的内存块,在分配内存的时候,地址8字节对齐(保证内存访问效率)。
准备工作的代码如下:
cpp
#include "malloc.h"
#include "string.h"
#include "stdbool.h"
#include "stdio.h"
#define TOTAL_MEMORY (1024*10)//管理的内存大小
#define BLOCK_SIZE 8 // 每个块8字节
#define HEADER_SIZE 8 // 头部8字节
#define BITMAP_SIZE (TOTAL_MEMORY / BLOCK_SIZE / 8) // 位图大小(字节)
typedef struct {
uint8_t *bitmap; // 位图数组
uint32_t total_blocks; // 总块数
uint32_t free_blocks; // 空闲块数
} BitmapHandleTypeDef;
//创建位图
uint8_t bitmap[BITMAP_SIZE];
//创建内部内存池
__attribute((aligned(8))) uint8_t InterMem[TOTAL_MEMORY];
//创建位图全局句柄
BitmapHandleTypeDef InterMemStructer;
/* 位图操作宏 */
#define BITMAP_SET(bitmap, index) ((bitmap)[(index)/8] |= (1U << ((index)%8)))
#define BITMAP_CLEAR(bitmap, index) ((bitmap)[(index)/8] &= ~(1U << ((index)%8)))
#define BITMAP_TEST(bitmap, index) ((bitmap)[(index)/8] & (1U << ((index)%8)))
这里在创建内存池的时候,使用__attribute((aligned(8)))来保证数组地址为8字节对齐,保证分配内存的时候,每一块的首地址都是8字节对齐。
位图操作宏的功能分别是置位,清除位以及查询位。
内存池初始化:
cpp
void malloc_Init(void)
{
//初始化位图以及内存池
memset(bitmap,0,BITMAP_SIZE);
memset(InterMem,0,TOTAL_MEMORY);
//初始化位图结构体
{
InterMemStructer.bitmap = bitmap;
InterMemStructer.free_blocks = TOTAL_MEMORY/BLOCK_SIZE;
InterMemStructer.total_blocks = TOTAL_MEMORY/BLOCK_SIZE;
}
}
对位图进行清零初始化,内存池数组清零可选。
mymalloc函数:
cpp
void* my_malloc(uint32_t bytes)
{
void* retadr = NULL;
uint32_t start = 0;
uint32_t needbytes = bytes + HEADER_SIZE;
uint32_t needblocks = (needbytes+7) / 8;//向上取整
uint32_t blockcount = 0;
bool isfree = false;
if(InterMemStructer.bitmap!=bitmap)malloc_Init();
if(bytes > 0)
{
//先查看是否有空余空间
for(uint32_t i = 0;i < BITMAP_SIZE * 8;i++)
{
if(!BITMAP_TEST(InterMemStructer.bitmap,i))//如果是空闲的块
{
if(isfree == false)//记录空闲块的起始位置
{
isfree = true;
start = i;
}
blockcount++;
if(blockcount == needblocks)//空闲块足够
{
uint8_t* block_start = (uint8_t*)&InterMem[start*8];//记录块起始地址
*(uint32_t*)block_start = needblocks;//记录本次申请的数据块大小
block_start += HEADER_SIZE;//地址偏移4字节
retadr = block_start;//把偏移后的地址赋值给返回值
InterMemStructer.free_blocks -= needblocks;//空闲块减少
for(uint32_t set = start;set < needblocks + start;set++)
{
//标记位图
BITMAP_SET(InterMemStructer.bitmap,set);
}
break;//跳出循环,不再查找空余块
}
}
else
{
start = 0;
isfree = false;
blockcount = 0;
}
}
}
else
{
return NULL;
}
return retadr;
}
这个函数的主要操作就是,查找位图中,是否有符合条件的连续的数据块,首次找到以后,就不再查找了,倘若没找到以及申请的内存大小不符合条件,就会直接返回空指针。值得一提的是,倘若申请1字节的大小,但仍然会占用16字节的空间,因为除了包含申请的空间以外,还要存储申请的块大小以及保证8字节对齐。
myfree函数:
cpp
void my_free(void* ptr)
{
if(ptr == NULL) return;
uint8_t* block_start = (uint8_t*)ptr - HEADER_SIZE;
uint32_t blocks = *((uint32_t*)block_start);
uint32_t start = (block_start - InterMem) / BLOCK_SIZE;
InterMemStructer.free_blocks += blocks;
for(uint32_t i = 0; i < blocks; i++)
{
BITMAP_CLEAR(InterMemStructer.bitmap, start + i);
}
}
myfree函数就显得比较简单了主要是计算偏移量,找到释放的是第几个块,体现在如下代码
cpp
uint32_t start = (block_start - InterMem) / BLOCK_SIZE;
for循环里针对数据头部的块数量进行位图的清零操作同时间接合并空闲内存块。
以上就是一个比较轻量化的内存池的管理算法,易于理解,但是在申请小内存的时候会占用一些不必要的内存空间,所以这个算法不适用于申请大量的小内存空间的情况下。
让AI写一个测试函数:
cpp
void correct_comprehensive_test(void)
{
printf("\r\n");
printf("===========================================\r\n");
printf(" 内存分配器正确性测试\r\n");
printf("===========================================\r\n");
int total_passed = 0;
int total_tests = 0;
// 测试1:初始化
printf("\r\n[1] 初始化测试\r\n");
malloc_Init();
printf(" ✓ 初始化完成\r\n");
total_passed++; total_tests++;
// 测试2:基本功能
printf("\r\n[2] 基本功能测试\r\n");
void* ptr1 = my_malloc(16);
if(ptr1) {
strcpy((char*)ptr1, "Test");
if(strcmp((char*)ptr1, "Test") == 0) {
printf(" ✓ 分配和写入测试\r\n");
}
my_free(ptr1);
printf(" ✓ 释放测试\r\n");
total_passed++;
}
total_tests++;
// 测试3:多块分配
printf("\r\n[3] 多块分配测试\r\n");
void* ptrs[5];
bool multi_ok = true;
for(int i = 0; i < 5; i++) {
ptrs[i] = my_malloc((i+1) * 20);
if(!ptrs[i]) {
multi_ok = false;
break;
}
}
if(multi_ok) {
printf(" ✓ 多块分配成功\r\n");
for(int i = 0; i < 5; i++) {
my_free(ptrs[i]);
}
printf(" ✓ 多块释放成功\r\n");
total_passed++;
}
total_tests++;
// 测试4:边界条件
printf("\r\n[4] 边界条件测试\r\n");
if(my_malloc(0) == NULL) {
printf(" ✓ 分配0字节返回NULL\r\n");
}
my_free(NULL);
printf(" ✓ 释放NULL指针安全\r\n");
total_passed++; total_tests++;
// 测试5:重新分配
printf("\r\n[5] 重新分配测试\r\n");
void* ptr = my_malloc(32);
my_free(ptr);
if(my_malloc(32)) {
printf(" ✓ 释放后重新分配成功\r\n");
total_passed++;
}
total_tests++;
// 测试6:碎片测试
printf("\r\n[6] 碎片测试\r\n");
malloc_Init();
void* f1 = my_malloc(16);
void* f2 = my_malloc(64);
void* f3 = my_malloc(32);
if(f1 && f2 && f3) {
printf(" ✓ 创建碎片环境\r\n");
my_free(f2);
if(my_malloc(48)) {
printf(" ✓ 在碎片中分配成功\r\n");
total_passed++;
}
my_free(f1);
my_free(f3);
}
total_tests++;
// 测试7:正确的内存池满测试
printf("\r\n[7] 内存池满测试\r\n");
malloc_Init();
// 分配直到接近满
void* small_blocks[200];
int small_count = 0;
// 每个分配8字节数据,需要2个块
while(InterMemStructer.free_blocks > 20) { // 保留20个块
small_blocks[small_count] = my_malloc(8);
if(small_blocks[small_count]) {
small_count++;
} else {
break;
}
if(small_count >= 200) break;
}
printf(" 已分配 %d 个小块,剩余 %u 块\r\n",
small_count, InterMemStructer.free_blocks);
// 尝试分配中等块(应该成功,因为还有空间)
void* medium = my_malloc(32);
if(medium) {
printf(" ✓ 内存紧张时分配中等块成功\r\n");
my_free(medium);
}
// 继续分配直到真正失败
while(my_malloc(8)) {
// 继续分配,直到失败
}
// 现在尝试分配(应该失败)
if(my_malloc(1) == NULL) {
printf(" ✓ 内存满时分配失败\r\n");
}
// 释放一些
for(int i = 0; i < small_count/2; i++) {
if(small_blocks[i]) {
my_free(small_blocks[i]);
}
}
// 再分配(应该成功)
if(my_malloc(16)) {
printf(" ✓ 释放后重新分配成功\r\n");
total_passed++;
}
// 清理
for(int i = small_count/2; i < small_count; i++) {
if(small_blocks[i]) {
my_free(small_blocks[i]);
}
}
total_tests++;
// 测试8:最终检查
printf("\r\n[8] 最终完整性检查\r\n");
malloc_Init();
// 分配释放循环
for(int i = 0; i < 20; i++) {
void* p = my_malloc((i % 5 + 1) * 16);
if(p) my_free(p);
}
if(InterMemStructer.free_blocks == TOTAL_MEMORY / BLOCK_SIZE) {
printf(" ✓ 最终完整性检查通过\r\n");
total_passed++;
}
total_tests++;
// 结果
printf("\r\n===========================================\r\n");
printf("测试结果汇总\r\n");
printf("===========================================\r\n");
printf("总测试项: %d\r\n", total_tests);
printf("通过项: %d\r\n", total_passed);
printf("失败项: %d\r\n", total_tests - total_passed);
printf("成功率: %.1f%%\r\n", (float)total_passed / total_tests * 100);
if(total_passed == total_tests) {
printf("\r\n🎉 所有测试通过!内存分配器工作正常!\r\n");
} else {
printf("\r\n⚠️ %d个测试失败,请检查代码\r\n", total_tests - total_passed);
}
printf("===========================================\r\n");
}
运行结果:
cpp
===========================================
内存分配器正确性测试
===========================================
[1] 初始化测试
✓ 初始化完成
[2] 基本功能测试
✓ 分配和写入测试
✓ 释放测试
[3] 多块分配测试
✓ 多块分配成功
✓ 多块释放成功
[4] 边界条件测试
✓ 分配0字节返回NULL
✓ 释放NULL指针安全
[5] 重新分配测试
✓ 释放后重新分配成功
[6] 碎片测试
✓ 创建碎片环境
✓ 在碎片中分配成功
[7] 内存池满测试
已分配 200 个小块,剩余 880 块
✓ 内存紧张时分配中等块成功
✓ 内存满时分配失败
✓ 释放后重新分配成功
[8] 最终完整性检查
✓ 最终完整性检查通过
===========================================
测试结果汇总
===========================================
总测试项: 8
通过项: 8
失败项: 0
成功率: 100.0%
🎉 所有测试通过!内存分配器工作正常!
===========================================