Memory Ground Plus 是一个轻量级的静态内存池管理组件,专为嵌入式系统和资源受限环境设计。该组件提供预分配内存的管理功能,支持动态内存块分配、释放以及碎片整理,有效避免内存泄漏和碎片化问题。
需要在堆栈一体的32KSRAM中实现对JPEG文件的解码目标芯片为:WCH582,
使用了picoJPEG这个开源库。由于还需要给蓝牙协议栈提供这个SRAM,故而剩余的
SRAM就颇为捉襟见肘,初始做法是将静态分配内存给组件&模块作为专用缓冲区,
但是发现这样非常消耗SRAM且内存利用率不高,一下就占满了整个SRAM。
随后使用tlsf这个开源库,作为内存管理组件,但是我又忽略了该库的内存控制块的大小,
在WCH58X工具链中,控制块初始大小有3144,我至少需要分配4KB的内存给这个内存管理组件。
于是乎,我精心想要削减SRAM的开销,实际上并没有削减太多,也就削了1KB多一些。
在GitHub上也没有找到符合我当下需求——纯C实现的简单内存管理库。于是我自己花3~4天时间,
实现了本内存管理库。
- ✅ 静态内存池管理:使用预分配的静态内存区域,避免动态内存分配的不确定性
- ✅ 内存块分配与释放:支持可变大小的内存块申请和释放
- ✅ 内存碎片整理:自动查找并利用已释放的空闲块,提高内存利用率
- ✅ 安全性检查:防止重复释放、非法地址释放等错误操作
- ✅ 内存块调整大小:通过
mgp_realloc()支持动态调整已分配内存块大小 - ✅ 可配置内存对齐:支持自定义对齐字节数(默认 4 字节),适配不同硬件需求
- ✅ 完善的测试套件:包含 27 个测试用例,覆盖基础功能、边界条件、压力测试等场景
在使用本项目前,请仔细阅读以下局限性说明:
- 单线程环境限制
- 当前实现未考虑线程安全
- 不适合多任务操作系统或多核处理器环境
- 如需在多线程环境使用,请自行添加互斥锁机制
-
内存池大小固定
- 创建时指定大小后无法动态扩容
- 必须预先评估最大内存需求
- 不适合内存需求变化剧烈的应用
-
无内存保护标记(当前版本)
- 临时移除了头尾魔术数保护功能
- 无法自动检测缓冲区溢出
- 后续版本将重新实现该功能
-
小块内存开销较大
- 每个内存块需要额外的控制块管理
- 大量小块分配时内存利用率较低
- 不适合频繁分配极小内存的场景(如几字节的数据)
-
外部碎片问题
- 虽然有碎片查找机制,但未实现内存紧缩
- 长期运行且频繁分配释放不同大小的块可能导致碎片积累
- 可能出现总空闲足够但无连续大块的情况
-
无内存初始化
- 分配的内存不会自动清零或填充特定值
- 用户必须手动初始化,否则可能包含垃圾数据
- 对齐要求:虽然支持自定义对齐字节数,但必须是 2 的幂次方(如 2/4/8/16)
- 错误处理:分配失败只返回 NULL,不提供详细错误信息
- DEBUG 配置:调试模式需在编译时确定,无法运行时动态调整
- 控制块数量:最多支持 32767 个已分配块(
short类型限制) - 嵌入式特殊需求:不支持 MPU 配置、中断上下文保护等功能安全特性
| 应用场景 | 推荐度 | 说明 |
|---|---|---|
| 嵌入式裸机系统 | ✅ 非常适合 | 单线程、确定性内存需求 |
| RTOS 多任务系统 | 需要添加互斥锁 | |
| 桌面/服务器应用 | ❌ 不推荐 | 建议使用系统 malloc/free |
| 实时系统 | 需评估最坏分配时间 | |
| 高并发网络服务 | ❌ 不适合 | 线程安全问题 |
| 长期运行设备 | 碎片积累风险 | |
| 安全关键系统 | ❌ 不适合 | 缺少功能安全特性 |
| 教学/学习用途 | ✅ 非常适合 | 代码简洁、原理清晰 |
💡 改进建议:如需突破这些局限性,可参考代码注释中的扩展点进行修改,或联系作者讨论定制方案。
memoryGroundPlus/
├── .lingma/ # Lingma IDE 配置
├── include/
│ ├── memGroundP.h # 主头文件(API 接口)
│ └── DBG_macro.h # 调试宏定义
├── sources/
│ ├── main.c # 主程序入口
│ ├── memGroundP.c # 内存池核心实现
│ └── mgp_test.c # 完整测试套件
├── build/ # 构建输出目录(自动生成)
│ └── bin/
│ └── outputFile.exe # 编译生成的可执行文件
├── CMakeLists.txt # CMake 构建配置
├── build.bat # Windows 一键构建脚本
├── TEST_GUIDE.md # 测试详细指南
└── README.md # 本说明文档
| 头文件 | 类型 | 用途 |
|---|---|---|
<stdint.h> |
标准库 | 提供 uint8_t、uintptr_t 等固定宽度类型 |
<string.h> |
标准库 | 提供 memset、memcpy 等内存操作函数 |
<stdio.h> |
标准库 | 提供 printf、sprintf 等输入输出函数 |
<stdlib.h> |
标准库 | 提供 malloc、free、qsort 等通用工具函数 |
<assert.h> |
标准库 | 提供断言检查(仅 DEBUG 模式) |
memGroundP.h |
项目文件 | 内存池模块公共接口 |
DBG_macro.h |
项目文件 | 调试日志宏定义 |
| 函数 | 所属库 | 用途 |
|---|---|---|
memset() |
<string.h> |
清空内存区域 |
memcpy() |
<string.h> |
内存块复制 |
printf() |
<stdio.h> |
格式化输出 |
sprintf() |
<stdio.h> |
字符串格式化 |
malloc() |
<stdlib.h> |
动态内存分配(仅测试中使用) |
free() |
<stdlib.h> |
释放动态内存(仅测试中使用) |
qsort() |
<stdlib.h> |
快速排序(控制块排序) |
rand() |
<stdlib.h> |
生成随机数(压力测试) |
srand() |
<stdlib.h> |
设置随机种子 |
assert() |
<assert.h> |
运行时断言检查 |
- 无第三方库依赖:本项目完全基于 C 标准库实现
- 编译器要求:支持 C11 标准的编译器(如 GCC、Clang、MSVC)
- 构建工具:CMake 3.10+、Ninja(推荐)或 Make
Windows (PowerShell)
.\build.bat或者手动编译
cd build
cmake .. -G Ninja
cmake --build .编译完成后,可执行文件位于 build/bin/outputFile.exe
.\build\bin\outputFile.exe#include "memGroundP.h"
// 1. 准备一块静态内存区域
static unsigned char memory_pool[1024];
int main(void)
{
// 2. 创建内存池
mgp_t pool = mgp_create_with_pool(memory_pool, sizeof(memory_pool));
if (pool == NULL) {
return -1;
}
// 3. 分配内存
void *block1 = mgp_malloc(pool, 64); // 分配 64 字节
void *block2 = mgp_malloc(pool, 128); // 分配 128 字节
// ... 使用分配的内存 ...
// 4. 释放内存
mgp_free(pool, block1);
mgp_free(pool, block2);
return 0;
}mgp_t mgp_create_with_pool(void *mem, size_t bytes);- 功能:使用给定的内存区域创建内存池
- 参数:
mem:预分配的内存区域起始地址bytes:内存区域总大小(字节)
- 返回值:成功返回内存池句柄,失败返回
NULL - 最小内存需求:
sizeof(mgp_pool_t) + sizeof(mgp_ctrl_t)≈ 72 字节
void *mgp_malloc(mgp_t poolAddr, const size_t bytes);- 功能:从内存池分配指定大小的内存块
- 参数:
poolAddr:内存池句柄bytes:需要的字节数(用户可用大小)
- 返回值:成功返回内存块指针,失败返回
NULL - 实际占用:
bytes字节(当前版本无头尾标记) - 对齐:自动按
MGP_ALIGN_NUM配置值对齐(默认 4 字节,可修改) - 配置方法:在
memGroundP.h中修改MGP_ALIGN_NUM宏定义
void mgp_free(mgp_t poolAddr, void *p);- 功能:释放已分配的内存块
- 参数:
poolAddr:内存池句柄p:要释放的内存块指针
- 注意:释放非法地址会触发错误提示
void* mgp_realloc(mgp_t poolAddr, const void* src, const size_t bytes);- 功能:调整已分配内存块的大小
- 参数:
poolAddr:内存池句柄src:原内存块指针(可为 NULL)bytes:新的字节数
- 返回值:成功返回新内存块指针,失败返回 NULL
- 特性:
src == NULL时等同于mgp_malloc()- 扩大时自动复制原数据
- 缩小时直接返回原指针
const size_t mgp_canAllocMaxSize(mgp_t p);- 功能:获取内存池可分配的最大内存块大小
- 参数:内存池句柄
- 返回值:可分配的最大字节数(用户可用大小)
| 等级 | 测试类型 | 用例数 | 说明 |
|---|---|---|---|
| Level 1 | 基础功能测试 | 4 | 内存池创建、尺寸查询等 |
| Level 2 | 内存分配测试 | 4 | 单次/多次分配、耗尽测试 |
| Level 3 | 内存释放测试 | 4 | 单次/乱序释放、异常释放检测 |
| Level 4 | 碎片整理测试 | 2 | 碎片再利用、尺寸适配 |
| Level 5 | 边界条件测试 | 4 | 零长度、极小/极大尺寸 |
| Level 6 | 数据完整性测试 | 3 | 边界测试、数据隔离性 |
| Level 7 | 压力测试 | 2 | 大量分配、随机混合操作 |
| Level 8 | realloc 测试 | 4 | NULL 指针、扩大、缩小、零尺寸 |
| 总计 | - | 27 | 通过率:100% ✅ |
#include "memGroundP.h"
int main(void)
{
// 一键运行所有测试
mgp_run_all_tests();
return 0;
}在 memGroundP.h 中配置:
#define MGP_DEBUG 1 // 启用调试模式(开启 assert 和日志)在 memGroundP.h 中配置对齐字节数:
#define MGP_ALIGN_NUM 4 // 对齐字节数,必须是 2 的幂次方可选值示例:
MGP_ALIGN_NUM = 2:2 字节对齐(最小开销)MGP_ALIGN_NUM = 4:4 字节对齐(默认,适合 32 位系统)MGP_ALIGN_NUM = 8:8 字节对齐(适合 64 位系统或 DMA 传输)MGP_ALIGN_NUM = 16:16 字节对齐(适合 SIMD 指令)MGP_ALIGN_NUM = 32:32 字节对齐(适合缓存行对齐)
注意事项:
⚠️ 必须是 2 的幂次方(如 2, 4, 8, 16, 32...)⚠️ 修改后需要重新编译整个项目⚠️ 较大的对齐值会增加内存开销,但可能提升访问性能
在 memGroundP.c 或 mgp_test.c 中配置详细日志:
#define MGP_CREATE_POOL_DEBUG 0 // 内存池创建调试
#define MGP_CTRL_BLOCK_DEBUG 0 // 控制块调试
#define MGP_FREE_BLOCK_DEBUG 0 // 空闲块查找调试
#define MGP_MEMORY_FREE_DEBUG 1 // 释放操作调试
#define MGP_MEMORY_ALLOC_DEBUG 1 // 分配操作调试┌─────────────────────────────────────────────────┐
│ 内存池整体布局 │
├─────────────────────────────────────────────────┤
│ [mgp_pool_t] ← 内存池控制结构(头部) │
│ [mgp_ctrl_t] ← 控制块表(紧随 pool_t) │
│ [已分配块 1] ← 动态分配的内存块 │
│ [已分配块 2] │
│ ... │
│ [未分配空间] ← 剩余可用空间 │
└─────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────┐
│ 单个内存块布局(当前版本) │
├─────────────────────────────────────────────────┤
│ [用户数据区] ← 用户实际使用的部分 │
│ (由控制块管理) │
│ (自动对齐) │
└─────────────────────────────────────────────────┘
> 💡 注:头尾保护标记功能暂时移除,后续版本将重新实现
>
> 💡 注:内存块地址会自动按 `MGP_ALIGN_NUM` 配置对齐
| 常量 | 值 | 说明 |
|---|---|---|
MGP_OK |
0 |
成功 |
MGP_ERR_ARG |
-1 |
参数错误 |
MGP_ERR_NOMEM |
-3 |
内存不足 |
MGP_ALIGN_NUM |
4 |
默认对齐字节数(可配置) |
#define MGP_OK 0 ///< 成功
#define MGP_ERR_ARG (-1) ///< 参数错误
#define MGP_ERR_ALIGN (-2) ///< 对齐错误
#define MGP_ERR_NOMEM (-3) ///< 内存不足
#define MGP_ERR_CORRUPT (-10) ///< 检验损坏(保留)
#define MGP_ERR_NOT_INIT (-20) ///< 未初始化- 最小分配尺寸:实际分配大小为
bytes字节(当前版本无头尾标记) - 不支持多线程:当前实现未考虑线程安全,如需在多线程环境使用请自行添加锁机制
- 零长度分配:分配 0 字节会触发 assert(预期行为)
- 内存池扩容:不支持动态扩容,需在创建时指定足够的大小
- 对齐要求:支持自定义对齐字节数,但必须是 2 的幂次方
- realloc 使用:调整后的内存块地址可能会变化,需更新引用指针
- 性能建议:对于 DMA 传输或缓存敏感应用,建议使用 8/16/32 字节对齐
-
v1.1 (2026-04-01)
- ✨ 新增
mgp_realloc()函数,支持动态调整内存块大小 - ✨ 新增可配置内存对齐功能(
MGP_ALIGN_NUM宏) - 🛠️ 优化
mgp_getFreeBlock()逻辑,改进碎片查找算法 - 🔧 临时移除魔术数保护功能(简化实现)
- ✅ 测试用例扩展至 27 个,保持 100% 通过率
- 📝 更新文档和测试指南
- ✨ 新增
-
v1.0 (2026-03-20)
- ✨ 初始版本发布
- ✅ 完整的 23 个测试用例,通过率 100%
- 🛠️ 修复边界条件问题(指针下溢风险)
- 🛠️ 添加最大分配尺寸检查
欢迎提交 Issue 和 Pull Request!
如有问题或建议,请查看:
- TEST_GUIDE.md - 详细测试指南
- 代码中的 Doxygen 注释
本项目采用 MIT 许可证(或其他你选择的许可证)
- 作者:___________
- Email:___________
- 项目主页:___________
Memory Ground Plus - 让内存管理更可靠!