参考
stm32的栈监控与HardFault_Handler问题排查
cMemTable.h
c
#ifndef __cMemTable__
#define __cMemTable__
#include <stdint.h>
#include "bitmap.h"
#ifdef __cplusplus
extern "C" {
#endif
//==================================================
// CmemTable:简化版内存分配器(bitmap + block 管理)
//==================================================
/*
这个结构体本质是一个"静态内存池管理器"
核心设计思想:
1. 将连续内存(mem_buf)
切分为固定大小 block
2. 用 bitmap 管理 block 是否占用
3. 用 mem_len 记录分配长度(只在起始 block)
==================================================
内存结构示意:
==================================================
mem_buf:
|----|----|----|----|----|----|
block_size = 64B
bitmap:
0 0 1 1 0 0 ...
mem_len:
[len][0][0][len]...
*/
//==================================================
// CmemTable 主结构
//==================================================
typedef struct {
//==================================================
// 内存数据区(真正存用户数据)
//==================================================
uint8_t *mem_buf;
/*
指向外部提供的大块内存
例:
malloc 或 静态数组
*/
//==================================================
// bitmap 管理区
//==================================================
uint8_t *mem_map;
/*
每一 bit 表示一个 block 是否被占用
0 = free
1 = used
*/
//==================================================
// 分配元信息(辅助 free)
//==================================================
uint16_t *mem_len;
/*
只在"起始 block"记录长度
例:
alloc 3 blocks
mem_len[start] = 3
mem_len[start+1] = 0
mem_len[start+2] = 0
用于 O(1) free
*/
//==================================================
// 内存池整体配置
//==================================================
uint32_t total_size;
/*
内存池总字节数
= mem_buf 实际大小
*/
uint32_t block_size;
/*
每个 block 的大小(字节)
例:
block_size = 32
→ 每 32 字节为一个分配单位
*/
uint32_t block_num;
/*
block 总数量
= total_size / block_size
*/
//==================================================
// bitmap 对象封装
//==================================================
bitmap_t bm;
/*
对 mem_map 的封装操作层
提供:
set / clear / find / get
*/
} CmemTable;
//==================================================
// C API 接口
//==================================================
/*
cmem_init
--------------------------------
初始化内存池
全部外部传入(无 malloc)
参数说明:
buf : 内存池
total_size : 总大小
block_size : block 大小
bitmap_buf : bitmap 存储区
len_buf : 每 block 长度表
*/
int cmem_init(CmemTable *m,
void *buf,
uint32_t total_size,
uint32_t block_size,
uint8_t *bitmap_buf,
uint16_t *len_buf);
//==================================================
// 内存分配
//==================================================
/*
cmem_alloc
按 block 分配连续空间
流程:
size → 转 block 数
bitmap_find_free
mark bitmap
写 mem_len
返回指针
*/
void* cmem_alloc(CmemTable *m, uint32_t size);
//==================================================
// 内存释放
//==================================================
/*
cmem_free
根据 ptr 找到起始 block
流程:
ptr → index
mem_len[index] = N
清 bitmap N 个 block
*/
void cmem_free(CmemTable *m, void *ptr);
//==================================================
// 统计接口
//==================================================
/*
cmem_total_size
返回内存池总容量(固定值)
*/
uint32_t cmem_total_size(CmemTable *m);
/*
cmem_free_size
当前剩余空闲空间
实现方式:
统计 bitmap 中 0 的数量
*/
uint32_t cmem_free_size(CmemTable *m);
/*
cmem_max_free_block
最大连续可分配空间
用于判断:
- 是否存在大块内存
- 是否发生严重碎片
*/
uint32_t cmem_max_free_block(CmemTable *m);
#ifdef __cplusplus
}
#endif
#endif
cMemTable.c
c
#include "cMemTable.h"
//==================================================
// cmem_init:初始化内存池
//==================================================
/*
作用:
把外部提供的 buffer / bitmap / len 绑定成内存池
本质:
"不分配内存的 malloc 初始化器"
==================================================
内存结构建立:
==================================================
mem_buf → 数据区
mem_map → bitmap(状态)
mem_len → 分配长度表
*/
int cmem_init(CmemTable *m,
void *buf,
uint32_t total_size,
uint32_t block_size,
uint8_t *bitmap_buf,
uint16_t *len_buf)
{
//==================================================
// 参数合法性检查
//==================================================
if (!m || !buf || !bitmap_buf || !len_buf || block_size == 0)
return -1;
//==================================================
// 绑定外部内存
//==================================================
m->mem_buf = (uint8_t*)buf;
m->mem_map = bitmap_buf;
m->mem_len = len_buf;
//==================================================
// 内存池参数配置
//==================================================
m->total_size = total_size;
m->block_size = block_size;
// block 数量 = 总大小 / block 大小
m->block_num = total_size / block_size;
//==================================================
// 初始化 bitmap
//==================================================
/*
所有 block 标记为 0(空闲)
*/
bitmap_init(&m->bm, m->mem_map, m->block_num);
//==================================================
// 初始化长度表
//==================================================
/*
mem_len 作用:
只在"起始 block"记录分配长度
例:
alloc 3 blocks
mem_len[start] = 3
*/
for (uint32_t i = 0; i < m->block_num; i++)
m->mem_len[i] = 0;
return 0;
}
//==================================================
// cmem_alloc:内存分配
//==================================================
/*
核心流程:
1. size → 转 block 数
2. bitmap 找连续空闲区
3. 标记 bitmap
4. 记录 mem_len
5. 返回地址
本质:
first-fit 连续分配器
*/
void* cmem_alloc(CmemTable *m, uint32_t size)
{
if (!m || size == 0) return NULL;
//==================================================
// 计算需要多少 block
//==================================================
uint32_t need = (size + m->block_size - 1) / m->block_size;
if (need > m->block_num)
return NULL;
//==================================================
// 在 bitmap 中找连续空闲区
//==================================================
int index = bitmap_find_free(&m->bm, need);
if (index < 0)
return NULL;
//==================================================
// 标记占用
//==================================================
for (uint32_t i = 0; i < need; i++)
bitmap_set(&m->bm, index + i);
//==================================================
// 记录长度(只记录起始 block)
//==================================================
m->mem_len[index] = need;
//==================================================
// 返回真实内存地址
//==================================================
return (void*)(m->mem_buf + index * m->block_size);
}
//==================================================
// cmem_free:释放内存
//==================================================
/*
核心思想:
ptr → block index → mem_len → 释放 bitmap
为什么 O(1)?
因为 mem_len 直接告诉你长度
*/
void cmem_free(CmemTable *m, void *ptr)
{
if (!m || !ptr) return;
uint8_t *p = (uint8_t*)ptr;
//==================================================
// 越界检查(防非法 ptr)
//==================================================
if (p < m->mem_buf || p >= m->mem_buf + m->total_size)
return;
//==================================================
// 计算 block 起始 index
//==================================================
uint32_t index = (p - m->mem_buf) / m->block_size;
//==================================================
// 获取分配长度
//==================================================
uint32_t len = m->mem_len[index];
if (len == 0) return;
//==================================================
// 清除 bitmap
//==================================================
for (uint32_t i = 0; i < len; i++)
bitmap_clear(&m->bm, index + i);
//==================================================
// 清除长度记录
//==================================================
m->mem_len[index] = 0;
}
//==================================================
// cmem_total_size:总容量
//==================================================
/*
返回内存池总大小(固定值)
*/
uint32_t cmem_total_size(CmemTable *m)
{
if (!m) return 0;
return m->total_size;
}
//==================================================
// cmem_free_size:剩余容量
//==================================================
/*
遍历 bitmap 统计 free block
时间复杂度:
O(n)
*/
uint32_t cmem_free_size(CmemTable *m)
{
if (!m) return 0;
uint32_t free_blocks = 0;
for (uint32_t i = 0; i < m->block_num; i++)
{
if (bitmap_get(&m->bm, i) == 0)
free_blocks++;
}
return free_blocks * m->block_size;
}
//==================================================
// cmem_max_free_block:最大连续空闲空间
//==================================================
/*
用于判断碎片情况
返回:
最大连续可分配字节数
算法:
统计最长连续 0 区间
*/
uint32_t cmem_max_free_block(CmemTable *m)
{
if (!m) return 0;
uint32_t max = 0;
uint32_t cur = 0;
for (uint32_t i = 0; i < m->block_num; i++)
{
if (bitmap_get(&m->bm, i) == 0)
{
cur++;
if (cur > max)
max = cur;
}
else
{
cur = 0;
}
}
return max * m->block_size;
}
bitmap.h
c
#ifndef __BITMAP_H__
#define __BITMAP_H__
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
//==================================================
// bitmap 概念说明
//==================================================
/*
bitmap(位图)是一种用 bit 来表示资源状态的数据结构:
1 bit = 1 个资源状态
常见含义:
0 -> 空闲(free)
1 -> 占用(used)
举例:
data = 0b00101100
index:
7 6 5 4 3 2 1 0
0 0 1 0 1 1 0 0
*/
//==================================================
// bitmap 对象
//==================================================
typedef struct
{
uint8_t *data; // 位图存储区(外部分配)
// 每个 bit 表示一个资源状态
int total_bits; // bitmap 总 bit 数
// 表示可管理的资源数量(如 block 数)
int byte_size; // bitmap 占用字节数
// = (total_bits + 7) / 8
} bitmap_t;
//==================================================
// 生命周期管理
//==================================================
/*
bitmap 不负责 malloc/free
只管理"外部提供的 buffer"
适用于:
- 嵌入式(无动态内存)
- 内存池
- RTOS资源管理
*/
// 初始化 bitmap
// buffer: 外部提供的存储空间
// total_bits: 可管理的 bit 数
int bitmap_init(bitmap_t *bm, uint8_t *buffer, int total_bits);
// 释放 / 重置 bitmap 结构(不释放 buffer)
void bitmap_deinit(bitmap_t *bm);
//==================================================
// 基础 bit 操作
//==================================================
/*
bitmap_set
--------------------------------
将 index 对应的 bit 置 1(占用)
例:
index = 3
data[0] |= (1 << 3)
*/
void bitmap_set(bitmap_t *bm, int index);
/*
bitmap_clear
--------------------------------
将 index 对应的 bit 清 0(释放)
例:
index = 3
data[0] &= ~(1 << 3)
*/
void bitmap_clear(bitmap_t *bm, int index);
/*
bitmap_get
--------------------------------
获取 index 对应 bit 的状态
返回:
0 -> 空闲
1 -> 已占用
*/
int bitmap_get(bitmap_t *bm, int index);
//==================================================
// 工具函数
//==================================================
/*
bitmap_reset
--------------------------------
清空整个 bitmap
等价于:
所有资源都设为 0(全部空闲)
*/
void bitmap_reset(bitmap_t *bm);
/*
bitmap_find_free
--------------------------------
查找连续空闲区间
参数:
need = 需要连续多少个 bit
返回:
>=0 : 起始 index
-1 : 没有足够连续空间
例子:
bitmap:
0 0 0 1 0 0 0 0
need = 3
返回 = 4
*/
int bitmap_find_free(bitmap_t *bm, int need);
#ifdef __cplusplus
}
#endif
#endif
bitmap.c
c
#include "bitmap.h"
//==================================================
// bitmap 生命周期初始化
//==================================================
/*
bitmap_init:初始化 bitmap 对象
本质作用:
把外部 buffer 绑定成 bitmap 存储区
参数:
bm : bitmap 对象
buffer : 外部提供的内存(存 bit)
total_bits : 可管理的 bit 数量
结构映射:
1 bit = 1 个资源
8 bit = 1 byte
*/
int bitmap_init(bitmap_t *bm, uint8_t *buffer, int total_bits)
{
// 参数合法性检查
if (!bm || !buffer || total_bits <= 0)
return -1;
// 绑定外部 buffer
bm->data = buffer;
// 总 bit 数(资源数量)
bm->total_bits = total_bits;
// 计算需要多少字节存储 bit
// (n + 7) / 8 = 向上取整
bm->byte_size = (total_bits + 7) / 8;
//==================================================
// 初始化 bitmap(全部清 0)
//==================================================
// 0 = 空闲状态
for (int i = 0; i < bm->byte_size; i++)
bm->data[i] = 0;
return 0;
}
//==================================================
// bitmap 销毁 / 解绑
//==================================================
/*
注意:
不释放 buffer(外部管理内存)
只清理结构体指针
适用于:
嵌入式 / 静态内存 / 内存池场景
*/
void bitmap_deinit(bitmap_t *bm)
{
if (!bm) return;
bm->data = 0;
bm->total_bits = 0;
bm->byte_size = 0;
}
//==================================================
// 设置 bit(标记占用)
//==================================================
/*
bitmap_set:
将 index 对应 bit 置 1
位定位方式:
byte_index = index >> 3 (除以8)
bit_index = index & 7 (对8取余)
示例:
index = 10
byte = 10 / 8 = 1
bit = 10 % 8 = 2
*/
void bitmap_set(bitmap_t *bm, int index)
{
bm->data[index >> 3] |= (1 << (index & 7));
}
//==================================================
// 清除 bit(释放资源)
//==================================================
/*
将某一位清 0
&= ~mask 作用:
只清目标 bit,其他 bit 保持不变
*/
void bitmap_clear(bitmap_t *bm, int index)
{
bm->data[index >> 3] &= ~(1 << (index & 7));
}
//==================================================
// 读取 bit 状态
//==================================================
/*
返回:
0 = 空闲
1 = 占用
*/
int bitmap_get(bitmap_t *bm, int index)
{
return (bm->data[index >> 3] >> (index & 7)) & 1;
}
//==================================================
// 清空 bitmap
//==================================================
/*
等价于:
所有资源全部置为"空闲状态"
常用于:
初始化 / reset / 重新分配
*/
void bitmap_reset(bitmap_t *bm)
{
for (int i = 0; i < bm->byte_size; i++)
bm->data[i] = 0;
}
//==================================================
// 查找连续空闲区间
//==================================================
/*
bitmap_find_free:
在 bitmap 中查找连续 need 个 0
返回:
>= 0 : 起始 index
-1 : 没有足够连续空间
================================
核心逻辑说明
================================
外层循环:
枚举起点 i
内层循环:
检查 i ~ i+need 是否全为 0
如果中间断掉:
j = 已连续长度
i += j - 1
跳过已检查区域(优化性能)
================================
举例:
0 0 0 1 0 0 0
need = 3
返回 index = 4
*/
int bitmap_find_free(bitmap_t *bm, int need)
{
// 参数检查
if (!bm || need <= 0 || need > bm->total_bits)
return -1;
// 遍历所有可能起点
for (int i = 0; i <= bm->total_bits - need; i++)
{
int j = 0;
// 检查连续是否为空闲
while (j < need && bitmap_get(bm, i + j) == 0)
{
j++;
}
// 找到完整连续区间
if (j == need)
{
return i;
}
// 优化:
// 跳过已验证区域,避免重复扫描
i += (j > 0) ? (j - 1) : 0;
}
return -1;
}
测试main.cpp
c
#include <stdio.h>
#include <stdint.h>
#include "cMemTable.h"
//==================================================
// 内存池配置
//==================================================
// 一共 1024 字节的连续内存空间
#define TOTAL_SIZE 1024
// 每次分配的最小单位
#define BLOCK_SIZE 32
uint8_t buffer[TOTAL_SIZE];
//==================================================
// block 数量32
//==================================================
#define BLOCK_NUM (TOTAL_SIZE / BLOCK_SIZE)
//==================================================
// 外部 bitmap & mem_len(必须与 cmem 匹配)
//==================================================
uint8_t bitmap[(BLOCK_NUM + 7) / 8];
uint16_t memlen[BLOCK_NUM];
//==================================================
// 打印统计信息
//==================================================
/*
用于观察内存池状态变化
三个核心指标:
1. total_size 总容量(固定)
2. free_size 当前剩余空间
3. max_free_block 最大连续空闲块(碎片指标)
*/
static void print_stat(CmemTable *m, const char *tag)
{
printf("\n=== %s ===\n", tag);
printf("total_size : %u\n", cmem_total_size(m));
printf("free_size : %u\n", cmem_free_size(m));
printf("max_free_block : %u\n", cmem_max_free_block(m));
}
//==================================================
// bitmap 可视化(核心调试工具)
//==================================================
/*
作用:
直观看 block 是否被占用
输出示例:
0011110000
含义:
0 = free
1 = used
*/
static void dump_bitmap(CmemTable *m)
{
printf("bitmap: ");
for (uint32_t i = 0; i < m->block_num; i++)
{
printf("%d", bitmap_get(&m->bm, i));
}
printf("\n");
}
//==================================================
// main 测试入口
//==================================================
int main()
{
CmemTable m;
//==================================================
// 初始化内存池
//==================================================
/*
把外部 buffer / bitmap / len 绑定到 cmem
此时 cmem 已经"拥有"整个内存池控制权
*/
cmem_init(&m,
buffer,
TOTAL_SIZE,
BLOCK_SIZE,
bitmap,
memlen);
print_stat(&m, "INIT");
dump_bitmap(&m);
//==================================================
// 1️⃣ 基础分配测试
//==================================================
/*
测试点:
- 不同 size 是否正确换算 block
- bitmap 是否正确标记
- mem_len 是否记录正确
*/
printf("\n=== ALLOC TEST ===\n");
//实际分配128
void *p1 = cmem_alloc(&m, 100);
printf("p1 = %p (100 bytes)\n", p1);
//实际分配224
void *p2 = cmem_alloc(&m, 200);
printf("p2 = %p (200 bytes)\n", p2);
//实际分配64
void *p3 = cmem_alloc(&m, 50);
printf("p3 = %p (50 bytes)\n", p3);
//剩余 1024-(128+224+64)=1024-416=608
dump_bitmap(&m);
print_stat(&m, "AFTER ALLOC");
//==================================================
// 2️⃣ 部分释放测试
//==================================================
/*
验证:
- free 是否只释放对应 block
- bitmap 是否正确回收
- free_size 是否变化
*/
printf("\n=== FREE TEST ===\n");
cmem_free(&m, p2);
printf("free p2\n");
dump_bitmap(&m);
print_stat(&m, "AFTER FREE p2");
//==================================================
// 3️⃣ 复用测试(关键)
//==================================================
/*
验证:
- 是否能复用刚释放的空间
- bitmap_find_free 是否正确工作
*/
printf("\n=== REUSE TEST ===\n");
void *p4 = cmem_alloc(&m, 180);
printf("p4 = %p (180 bytes)\n", p4);
dump_bitmap(&m);
print_stat(&m, "AFTER REUSE");
//==================================================
// 4️⃣ 全释放测试
//==================================================
/*
验证:
- 内存是否完全释放
- bitmap 是否全部恢复 0
- max_free_block 是否恢复最大
*/
printf("\n=== FULL FREE ===\n");
cmem_free(&m, p1);
cmem_free(&m, p3);
cmem_free(&m, p4);
dump_bitmap(&m);
print_stat(&m, "AFTER FULL FREE");
//==================================================
// 5️⃣ 边界测试
//==================================================
/*
测试非法输入:
- 超大申请
- 0 字节申请
目的:
验证 allocator 的鲁棒性
*/
printf("\n=== EDGE TEST ===\n");
void *p5 = cmem_alloc(&m, TOTAL_SIZE + 1);
printf("oversize alloc = %p (should be NULL)\n", p5);
void *p6 = cmem_alloc(&m, 0);
printf("zero alloc = %p (should be NULL)\n", p6);
print_stat(&m, "END");
return 0;
}
测试输出
bash
C:\Users\PC\CLionProjects\untitled14\cmake-build-debug\untitled14.exe
=== INIT ===
total_size : 1024
free_size : 1024
max_free_block : 1024
bitmap: 00000000000000000000000000000000
=== ALLOC TEST ===
p1 = 00406040 (100 bytes)
p2 = 004060C0 (200 bytes)
p3 = 004061A0 (50 bytes)
bitmap: 11111111111110000000000000000000
=== AFTER ALLOC ===
total_size : 1024
free_size : 608
max_free_block : 608
=== FREE TEST ===
free p2
bitmap: 11110000000110000000000000000000
=== AFTER FREE p2 ===
total_size : 1024
free_size : 832
max_free_block : 608
=== REUSE TEST ===
p4 = 004060C0 (180 bytes)
bitmap: 11111111110110000000000000000000
=== AFTER REUSE ===
total_size : 1024
free_size : 640
max_free_block : 608
=== FULL FREE ===
bitmap: 00000000000000000000000000000000
=== AFTER FULL FREE ===
total_size : 1024
free_size : 1024
max_free_block : 1024
=== EDGE TEST ===
oversize alloc = 00000000 (should be NULL)
zero alloc = 00000000 (should be NULL)
=== END ===
total_size : 1024
free_size : 1024
max_free_block : 1024
进程已结束,退出代码为 0