单片机内存管理CmemTable

参考

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
相关推荐
济6172 小时前
FreeRTOS 控制任务设计 (2)--- 运动学逆解 + PID 闭环 + PWM 驱动全流程实现
stm32·单片机·嵌入式·freertos
szxinmai主板定制专家2 小时前
基于RK3588超小体积,轻巧,长续航的无人机AI模块,支持视频跟踪
arm开发·人工智能·嵌入式硬件·fpga开发·无人机
小柯博客3 小时前
STM32MP2 RIF资源隔离框架详解:从架构到实践
网络·stm32·单片机·嵌入式硬件·架构·嵌入式·yocto
SunAqua3 小时前
《MCU与DSP芯片笔记》一、DSP芯片TI C2000系列TMS320F28035
笔记·单片机·嵌入式硬件
listhi5203 小时前
基于STM32的高精度电子秤设计与实现
stm32·单片机·嵌入式硬件
Hello_Embed4 小时前
嵌入式上位机开发入门(二十九):JsonRPC TCP Server
网络·单片机·网络协议·tcp/ip·json·嵌入式
笨笨饿4 小时前
# 67_MCU的几大分区
数据结构·单片机·嵌入式硬件·算法·机器人·线性回归·个人开发
SDAU20054 小时前
CH552的时钟应用
stm32·单片机·嵌入式硬件
实在太懒于是不想取名4 小时前
STM32N6的开发日记(6):用ISP中间件点亮IMX335相机的专业画质
stm32·嵌入式硬件·接口隔离原则