而如果想要使用 RAM_D2、RAM_D3,就要自己想辙了。这个时候,我选择设计一款内存动态分配释放器,实现 malloc()、realloc()、free(),岂不是香的很?然后很多的第三方库比如 miniz 压缩库等需要对少量内存进行动态分配释放的时候,我们有内存分配器,就可以顺利移植这些库进来。
特性
- 较低的内存开销(每个内存块头部有一个
Word 长度的变量用于标记内存块)
- 只要
ALLOC_MEMORY_ADDRESS 是对齐的,那么分配出来的内存也是按 ALLOC_MEMORY_ADDRESS 对齐的。
- 可在中断处理过程里调用。
- 遍历堆以分配内存(O(N)复杂度,适合单片机小内存管理)
- 较为简单的代码实现。
- 有堆异常检测,有重复释放检测。
- 分配时会根据分配的空间需求来尝试合并碎片块。
- 允许调用
DynAlloc_Alloc(0)、DynAlloc_Free(0)、DynAlloc_Realloc(0, 0)
代码
废话不多说,直接提供代码。经过测试,目前没发现问题。
dynalloc.h
#ifndef INC_DYNALLOC_H_
#define INC_DYNALLOC_H_ 1
#ifndef ALLOC_MEMORY_ADDRESS
#define ALLOC_MEMORY_ADDRESS 0x30000000
#define ALLOC_MEMORY_SIZE ((128 + 128 + 32) * 1024)
#define ALLOC_MEMORY_ALIGNMENT 8
#endif
#include <stddef.h>
// This header file provides `__disable_irq()` and `__enable_irq()`
#include "stm32h7xx_hal.h"
extern void Error_Handler();
void DynAlloc_Init();
void *DynAlloc_Alloc(size_t size);
void *DynAlloc_Realloc(void *user_ptr, size_t new_size);
void DynAlloc_Free(void *addr);
#endif /* INC_DYNALLOC_H_ */
dynalloc.c
#include "dynalloc.h"
#include <stdint.h>
#include <string.h>
#define ALLOC_MEMORY_ADDRESS_END (ALLOC_MEMORY_ADDRESS) + (ALLOC_MEMORY_SIZE)
#define ALLOC_MEMORY_ADDRESS_MAX ((ALLOC_MEMORY_ADDRESS_END) - 1)
#if ALLOC_MEMORY_SIZE > 0x7FFFFFFF
typedef uint64_t asize_t;
static const uint64_t SizeMask = 0x7fffffffffffffff;
static const uint64_t UsageMask = 0x8000000000000000;
#define ASIZE_LEN 8
#elif ALLOC_MEMORY_SIZE > 0x7FFF
typedef uint32_t asize_t;
static const uint32_t SizeMask = 0x7fffffff;
static const uint32_t UsageMask = 0x80000000;
#define ASIZE_LEN 4
#elif ALLOC_MEMORY_SIZE > 0x7F
typedef uint16_t asize_t;
static const uint16_t SizeMask = 0x7fff;
static const uint16_t UsageMask = 0x8000;
#define ASIZE_LEN 2
#else
typedef uint8_t asize_t;
static const uint8_t SizeMask = 0x7f;
static const uint8_t UsageMask = 0x80;
#define ASIZE_LEN 1
#endif
#define ALIGN_UP(x) (((x) - 1) / (ALLOC_MEMORY_ALIGNMENT) + 1) * (ALLOC_MEMORY_ALIGNMENT)
#pragma pack(push, 1)
typedef struct MemBlockTag_s
{
// Block status
asize_t block_state;
// paddings
#if (ALLOC_MEMORY_ALIGNMENT) - ASIZE_LEN > 0
uint8_t _pad[(ALLOC_MEMORY_ALIGNMENT)-ASIZE_LEN];
#endif
// The pointer to the allocated data
uint8_t data[0];
}MemBlockTag_t;
#pragma pack(pop)
void HeapCorrupted()
{
Error_Handler();
}
void DoubleFree()
{
Error_Handler();
}
void BadFree()
{
Error_Handler();
}
void BadReallocPtr()
{
Error_Handler();
}
static size_t GetBlockSize(MemBlockTag_t *b)
{
return b->block_state & SizeMask;
}
static void SetBlockSize(MemBlockTag_t *b, size_t size)
{
b->block_state &= ~SizeMask;
b->block_state |= size;
}
static int GetIsInuse(MemBlockTag_t *b)
{
return b->block_state & UsageMask;
}
static void SetIsInuse(MemBlockTag_t *b, int val)
{
if (val)
b->block_state |= UsageMask;
else
b->block_state &= ~UsageMask;
}
static void SetBlockState(MemBlockTag_t *b, size_t size, int is_inuse)
{
SetBlockSize(b, size);
SetIsInuse(b, is_inuse);
}
static MemBlockTag_t *GetBlockTagFromUserPtr(void *ptr)
{
MemBlockTag_t *ret = (MemBlockTag_t *)((size_t)ptr - (sizeof * ret));
if ((size_t)ret + GetBlockSize(ret) >= ALLOC_MEMORY_ADDRESS_END) HeapCorrupted();
return ret;
}
static void *GetUserPtr(MemBlockTag_t *ptr)
{
return (void*)&ptr->data[0];
}
static MemBlockTag_t *NextBlock(MemBlockTag_t *cur)
{
return (MemBlockTag_t *)((size_t)cur + GetBlockSize(cur));
}
static size_t GetContinuousFreeSize(MemBlockTag_t *ptr, size_t target_size)
{
size_t sum = 0;
while (!GetIsInuse(ptr))
{
sum += GetBlockSize(ptr);
ptr = NextBlock(ptr);
if ((size_t)ptr >= ALLOC_MEMORY_ADDRESS_END) break;
if (sum >= target_size) break;
}
return sum;
}
static void MakeFreeSpace(MemBlockTag_t *ptr, size_t size)
{
SetBlockState(ptr, size, 0);
}
void DynAlloc_Init()
{
MemBlockTag_t *ptr = (MemBlockTag_t *)ALLOC_MEMORY_ADDRESS;
__disable_irq();
MakeFreeSpace(ptr, ALLOC_MEMORY_SIZE);
__enable_irq();
}
void *DynAlloc_Alloc(size_t size)
{
MemBlockTag_t *ptr = (MemBlockTag_t *)ALLOC_MEMORY_ADDRESS;
size_t block_size_to_alloc = ALIGN_UP(size + (sizeof * ptr));
__disable_irq();
while (1)
{
size_t block_size = GetBlockSize(ptr);
if (GetIsInuse(ptr))
{
ptr = NextBlock(ptr);
if ((size_t)ptr >= ALLOC_MEMORY_ADDRESS_MAX)
{
__enable_irq();
return NULL;
}
}
else
{
size_t free_space = GetContinuousFreeSize(ptr, block_size_to_alloc);
if (free_space < block_size_to_alloc)
{
MakeFreeSpace(ptr, free_space);
ptr = NextBlock(ptr);
continue;
}
else
{
MemBlockTag_t *ret = ptr;
size_t next_free_space = free_space - block_size_to_alloc;
if (next_free_space <= sizeof * ptr)
block_size_to_alloc = free_space;
SetBlockState(ret, block_size_to_alloc, 1);
if (block_size == block_size_to_alloc) return GetUserPtr(ret);
ptr = NextBlock(ret);
MakeFreeSpace(ptr, free_space - block_size_to_alloc);
__enable_irq();
return GetUserPtr(ret);
}
}
}
}
void *DynAlloc_Realloc(void *user_ptr, size_t new_size)
{
MemBlockTag_t *ptr;
size_t block_size_to_alloc;
size_t block_size;
size_t avail_space;
if (user_ptr == NULL) return DynAlloc_Alloc(new_size);
if (!new_size)
{
DynAlloc_Free(user_ptr);
return NULL;
}
if ((size_t)user_ptr < ALLOC_MEMORY_ADDRESS || (size_t)user_ptr > ALLOC_MEMORY_ADDRESS_MAX) BadReallocPtr();
__disable_irq();
ptr = GetBlockTagFromUserPtr(user_ptr);
block_size = GetBlockSize(ptr);
block_size_to_alloc = ALIGN_UP(new_size + sizeof * ptr);
if (block_size_to_alloc == block_size)
{
__enable_irq();
return user_ptr;
}
if (block_size_to_alloc > block_size)
avail_space = block_size + GetContinuousFreeSize(NextBlock(ptr), block_size_to_alloc - block_size);
else
avail_space = block_size;
if (avail_space >= block_size_to_alloc)
{
size_t tail_size = avail_space - block_size_to_alloc;
if (tail_size > sizeof * ptr)
{
SetBlockSize(ptr, block_size_to_alloc);
ptr = NextBlock(ptr);
MakeFreeSpace(ptr, tail_size);
}
__enable_irq();
return user_ptr;
}
else
{
__enable_irq();
void *new_addr = DynAlloc_Alloc(new_size);
if (!new_addr) return NULL;
memcpy(new_addr, user_ptr, GetBlockSize(ptr));
DynAlloc_Free(user_ptr);
return new_addr;
}
}
void DynAlloc_Free(void *addr)
{
MemBlockTag_t *ptr;
MemBlockTag_t *next_ptr;
if (!addr) return;
if ((size_t)addr < ALLOC_MEMORY_ADDRESS || (size_t)addr > ALLOC_MEMORY_ADDRESS_MAX)
{
BadFree();
return;
}
__disable_irq();
ptr = GetBlockTagFromUserPtr(addr);
if (!(GetIsInuse(ptr)))
{
__enable_irq();
DoubleFree();
return;
}
SetIsInuse(ptr, 0);
next_ptr = NextBlock(ptr);
if ((size_t)next_ptr <= ALLOC_MEMORY_ADDRESS_MAX && !GetIsInuse(next_ptr))
{
size_t free_space = GetBlockSize(ptr) + GetBlockSize(next_ptr);
SetBlockSize(ptr, free_space);
}
__enable_irq();
}
你需要提供一个函数 Error_Handler(),一旦出现内存管理上的错误比如堆错误或者重复释放、错误释放错误,这个函数被调用,你在调试的时候可以根据调用栈判断具体的出错原因(堆错误、重复释放、错误释放)。
void Error_Handler()
{
for(;;);
}
程序开始运行时,需要先调用 DynAlloc_Init() 初始化堆,然后就可以分配、释放内存了。
行为
每一块内存区域的开头,被一个「内存区域标记」结构体管理,其标记内存块大小,以及是否被占用。
-
调用 DynAlloc_Init() 时,其仅在堆起始地址加上一块内存区域标记,把整块内存标记为未使用。
-
调用 DynAlloc_Alloc() 分配内存时,从堆起始地址开始 遍历堆(时间复杂度为 O(N) 因此不能用于大片内存的分配释放管理),找到空闲内存块后,持续向后探测多个空闲内存块直到找到足够大的连续的空闲内存块,将开头的部分设置为已占用,后续的部分合并为一个空闲内存块。
- 如果后续的空闲内存长度小于「内存区域标记」结构体的大小,那就无法分割。此时,这块的空闲内存被直接合并给这次分配内存的区块里面去。
- 将「内存区域标记」后面的内存的起始地址返回给用户。
-
用户调用 DynAlloc_Relloc() 进行内存分配大小调整时,先检测参数。比如旧指针如果是 NULL 则直接返回 DynAlloc_Alloc() 的结果;如果 new_size 为零则调用 DynAlloc_Free() 释放内存。否则,进行内存大小的调整。
- 调整时,看当前内存块与后面的空闲内存块加起来够不够大;如果够大,就把当前内存块的大小往后扩展,并重新标记后续的内存块大小;而如果不够标记(空闲内存长度低于「内存区域标记」长度),就干脆吞掉。
- 如果后续空闲内存不够,那就直接重新分配内存,拷贝数据到新内存,释放当前内存,返回新地址。
-
用户调用 DynAlloc_Free() 时,先会检查指针的范围是不是我们管理的内存地址的范围,不是就调用 BadFree(),其调用 Error_Handler();然后检查内存块的标记,看是不是有重复释放,有就调用 DoubleFree(),其调用 Error_Handler();是不是「非法内存块」(判断内存块大小是否不正确),是的话调用 HeapCorrupted(),其调用 Error_Handler()。