写本文中的初衷是一开始工作中使用到的是原子哥写得映射表法的内存分配函数,后面发现用它来作为cJSON库的内存申请函数时总会出现问题,内存利用率不是很高。又让AI写了一个,也就是基于heap_4的这个内存管理。两种方法我都读懂了,综合来看,我认为链表法确实比内存映射表法优点多!



链表管理法(AI写的):
实测能用!另外,在实时操作系统中在调用内存分配函数时最好使用互斥量判断一下!下一篇文章,我准备学习一下互斥量和信号量!以及经典的优先级翻转问题


 

一、这份代码是基于FreeRTOS heap_4,AI开发的

✅ 优点:
  1. 内存利用率极高

    • 申请 1 字节,实际消耗 = 头大小 + 对齐(1字节)。如果头是8字节,对齐4字节,总共只消耗 12 字节。

    • 适合频繁申请不定长内存的场景。

  2. 扩展性好

    • 可以支持非连续的堆内存(比如 STM32 有内部 SRAM 和外部 SDRAM,链表法可以把它们串起来一起管,而映射表法很难做到)。

下面是我对于这份内存管理的问题,AI的解读:

问题 1:NULL 永远会在最后吗?

答案:是的,绝对是的。

NULL 在这里起到的作用就是 “路尽头”的交通标志

  1. 初始状态
    刚初始化时,只有一个大块。它的 next 被显式赋值为 NULL。
    [ 唯一的巨大块 ] -> NULL

  2. 切割(Split)时
    当你从最后一块切出一部分时,新切出来的块(New Block)会继承原来块的 next。
    如果原来块的 next 是 NULL,那么新块的 next 也就成了 NULL。
    [ 块A ] -> [ 新块B ] -> NULL

  3. 合并(Merge)时
    如果倒数第二个块和最后一个块合并了,倒数第二个块的 next 会变成最后一个块的 next(也就是 NULL)。
    [ 合并后的大块 ] -> NULL

结论:
无论你怎么切、怎么合,链表的最后一个物理块的 next 指针永远指向 NULL。这是遍历链表时判断“是不是走到头了”的唯一依据。


问题 2:链表的结构顺序(A -> B -> C)和 heap 大数组的内存顺序永远会一致吗?

答案:是的,永远一致。

这也是这种内存管理算法(基于物理地址的链表)最特殊的特征。

1. 为什么一致?

因为我们的“链表节点”(也就是 mem_block_t 头结构体)不是在别的地方 malloc 出来的,而是直接印在那一大条 heap_ram 数组里的。

想象一条长长的法棍面包(heap_ram):

  1. 初始:整个面包是一个块。

  2. malloc:你从中间切了一刀。面包变成了 [左半段] 和 [右半段]

    • 物理上:左半段肯定在右半段前面。

    • 逻辑上:我们将左半段的 next 指向右半段。

  3. 结果:链表的指向方向,永远是从 低地址 指向 高地址

Address(A) < Address(B) < Address(C)

A->next == B, B->next == C


未满足 if (!curr->used && curr->size >= needed_size)条件而跳转到下一节点的情况

我们假设现在的内存条(heap_ram)已经被切分成了几个部分,链表长这样:

[块A] -> [块B] -> [块C] -> [块D] -> NULL

设定场景:

  • 块A已占用,大小 100 字节。

  • 块B空闲,但很小,只有 10 字节

  • 块C空闲,很大,有 60 字节

  • 块D已占用,大小 200 字节。

现在,用户代码调用了:my_malloc(30)。

  • needed_size(对齐后) = 32 字节

  • 判断条件是:if (!curr->used && curr->size >= 32)

我们开始遍历链表:


情况一:遇到“有人住的房子” (Block A)

  1. 当前状态:curr 指向 块A

  2. 检查条件

    • !curr->used:检查块A是否空闲。

    • 事实:块A used == 1(被占用了)。

    • 结果:!1 为 False。

  3. C语言特性(短路求值)

    • 因为 && 左边已经是 False 了,电脑甚至懒得去看右边的 size 够不够大。

    • 整个 if 条件不成立

  4. 心理活动

    “哎呀,这个房间(块A)虽然很大(100字节),但是里面已经住着人了(用户存了数据)。我不能把新客人塞进去,否则会打架(数据覆盖)。赶紧走,去看看下一间。”

  5. 执行动作

    • 跳过 if 大括号。

    • 执行 curr = curr->next;

    • curr 现在指向 块B


情况二:遇到“太小的空房子” (Block B)

  1. 当前状态:curr 指向 块B

  2. 检查条件

    • !curr->used:检查块B是否空闲。

    • 事实:块B used == 0(空的)。通过第一关!

    • curr->size >= 32:检查大小是否足够。

    • 事实:块B size == 10。

    • 计算:10 >= 32 为 False。

    • 结果:True && False 为 False。整个 if 条件不成立。

  3. 心理活动

    “哟,这个房间(块B)是空的!太好了……等等,客人需要 32平米,这个房间只有 10平米。塞不进去啊,强行塞进去会把墙撑破(内存越界到下一个块)。没办法,只能放弃这间,去看看下一间。”

  4. 执行动作

    • 跳过 if 大括号。

    • 执行 curr = curr->next;

    • curr 现在指向 块C


情况三:遇到“完美的房子” (Block C) —— 终于不跳了!

  1. 当前状态:curr 指向 块C

  2. 检查条件

    • !curr->used:块C是空的。True

    • curr->size >= 32:块C有 60字节,60 >= 32。True

    • 结果:True && True 为 True

  3. 心理活动

    “太棒了!这间是空的,而且够大!就选它了!”

  4. 执行动作

    • 进入 if 内部!

    • 执行切割(Split)、标记(Used=1)、返回(Return)。

    • 函数结束,不再执行 curr = curr->next。


情况四:悲剧的结尾 (假设块C也被占用了)

如果 块C 也是占用的,或者是空的但只有 20 字节(太小),那么会发生什么?

  1. 在块C处:条件不满足 -> curr = curr->next -> curr 指向 块D

  2. 在块D处:块D是占用的 -> 条件不满足 -> curr = curr->next。

  3. 链表尽头:块D 的 next 是 NULL。

    • 执行完 curr = curr->next 后,curr 变成了 NULL

  4. 回到 while 循环头

    • 检查 while (curr != NULL)。

    • curr 是 NULL,循环条件不成立。

  5. 退出循环

    • 执行 while 循环后面的 return NULL;。

    • 分配失败(OOM)。



my_malloc.c

#include "mymalloc.h"
#include "string.h"


/* --------------------------------------------------------------------------
 * 宏定义与结构体
 * -------------------------------------------------------------------------- */

// 向上对齐宏:将 x 对齐到 MEM_ALIGNMENT 的倍数
#define ALIGN_UP(x)  (x + (ALIGN_BASE - 1) & (~ALIGN_BASE - ))//这个宏只能用于 2 的幂次(2, 4, 8, 16...)的对齐。
//因为 4 的倍数,在二进制里有一个特征:最后两位必须是 00。
// 4: 0100
// 8: 1000
// 12: 1100
// 32: 100000
// 假如x = 30,二进制为 0001 1110,MEM_ALIGNMENT,为4,加上 MEM_ALIGNMENT - 1 = 3,为33, == > 00100001
//然后再通过 & ~(MEM_ALIGNMENT - 1) 操作,将最后两位清0,转变为4的倍数!为 00100000,转换为10进制为 32,即 30 对齐到 4 的倍数为 32。



/* 内存块头结构体,这是 C 语言中最经典、最合法的写法,叫做“自引用结构体” (Self-Referential Structure)。 */
/* 编译器是从上往下解析的。
当读到第一行 struct mem_block 时,编译器记住了:“哦,有个结构体叫 mem_block”。
当读到第二行 struct mem_block *next; 时,编译器想:“你要定义一个指针,指向 struct mem_block 类型。”
关键点:在 C 语言中,所有类型的指针(32位系统下)大小都是固定的 4 字节!
虽然编译器此时还不知道 struct mem_block 里面具体有多少个成员,总大小是多少,但它知道指向它的指针一定是 4 个字节。
所以,编译器能够确定 next 成员占用多少内存,也就允许编译通过。
如果不加指针 *,直接包含自己,那就是错误的:
struct mem_block {
    struct mem_block next; // ? 错误!禁止套娃!
    int size;
};
原因:如果要包含结构体本身,那就是无限递归:结构体里有个结构体,那个结构体里又有个结构体……大小无穷大,编译器无法计算大小,所以报错。*/
typedef struct mem_block
{
    struct mem_block *next; // 指向下一个物理相邻的块 4
    uint32_t size;          // 本块数据区的大小 (不包含头) 4 
    uint8_t used;           // 标记:1=占用,0=空闲 1
    uint8_t padding[3];     // 填充字节,保证结构体本身是4字节对齐的(假设32位系统) 3
} mem_block_t;//共12字节


/* 计算块头本身的大小 (必须对齐) */
#define BLOCK_HEAD_SIZE ALIGN_UP(sizeof(mem_block_t))

/* 静态大数组定义 */

#if define(CCM_RAM) || define(__CCM_RAM)
__attribute__((section(".ccmram"))) __attribute__((aligned(4))) static uint32_t heap[MY_HEAP_SIZE / 4];
#else
static uint32_t heap[MY_HEAP_SIZE / 4];
#endif

static mem_block_t *head_block = NULL;


/* --------------------------------------------------------------------------
 * 核心实现
 * -------------------------------------------------------------------------- */

void mymem_init(void)
{
    // 强制类型转换,把数组首地址当做第一个块的头
    head_block = (mem_block_t *)heap_ram;

    // 初始状态:整个数组是一个巨大的空闲块
    // 可用大小 = 总大小 - 头大小
    // 注意:这里 heap_ram 是 uint32_t 数组,转成字节大小时要 sizeof
    head_block->size = sizeof(heap_ram) - BLOCK_HEAD_SIZE;
    head_block->used = 0;
    head_block->next = NULL;
}


void *my_malloc(size_t size)
{
    mem_block_t *curr = head_block;
    if(size == 0 || curr == NULL)
        return NULL;

    // 1. 对齐请求的大小
    uint32_t need_size = ALIGN_UP(size);
    
    
    // 2. 遍历链表 (首次适应算法 First Fit)
    while(curr != NULL) {
        
        if(!curr->used && curr->size >= need_size) {//这里不考虑头的大小,头的大小在每次创建新块的时候留出来
            // 3. 检查是否需要分割 (Split)
            // 情况 A:需要切割 (当前剩余内存总大小 >= 需求 + 头大小 + 至少4字节数据)
            // 如果剩余空间还能放得下一个头 + 至少4字节数据,就切割
            if (curr->size >= needed_size + BLOCK_HEAD_SIZE + MEM_ALIGNMENT)
            {

                // 计算分割出来的“新空闲块”的地址
                // 地址 = 当前块头地址 + 头大小 + 分配给用户的数据大小
                uint8_t *next_ptr = (uint8_t *)curr + BLOCK_HEAD_SIZE + needed_size;
                mem_block_t *new_block = (mem_block_t *)next_ptr;

                // 初始化新块
                new_block->used = 0;
                new_block->size = curr->size - needed_size - BLOCK_HEAD_SIZE;//减去新块的头BLOCK_HEAD_SIZE大小 + 需要的字节大小
                new_block->next = curr->next; // 插入链表,新块指向当前块的下一个块

                // 更新当前块
                curr->size = needed_size;
                curr->next = new_block;
            }
            //情况 B:不需要切割 (大小刚好)

            // 4. 标记为已使用
            curr->used = 1;

            // 5. 返回数据区的地址 (跳过头)
            return (void *)((uint8_t *)curr + BLOCK_HEAD_SIZE);
            
        }
        //curr这个内存不是空闲的或者说
        curr = curr->next;
    }


}

void my_free(void *ptr)
{
    mem_block_t *curr;

    if (ptr == NULL)
    {
        return;
    }

    // 1. 根据用户指针反推块头指针
    curr = (mem_block_t *)((uint8_t *)ptr - BLOCK_HEAD_SIZE);
    // 简单的越界检查
    if ((uint8_t *)curr < (uint8_t *)heap_ram ||
        (uint8_t *)curr >= (uint8_t *)heap_ram + sizeof(heap_ram))
    {
        return;
    }
    
    if(curr->used == 0)
        return;
    
    // 2. 标记为空闲
    curr->used = 0;

    // 3. 尝试向后合并 (Coalescing)
    // 如果后面紧挨着的块也是空的,就把它们融合成一个大块
    while (curr->next != NULL)
    {
        if (curr->next->used == 0)
        {
            // 合并逻辑:当前大小 += 下一个头 + 下一个数据
            curr->size += BLOCK_HEAD_SIZE + curr->next->size;

            // 链表指针跳过下一个块
            curr->next = curr->next->next;

            // 继续循环,看看合并后的后面那个是不是还是空的
        }
        else
        {

            // 后面那个是占用的,合并不了,退出
            break;
        }
    }
}



uint32_t my_mem_get_free_size(void)
{
    mem_block_t *curr = head_block;
    uint32_t total_free = 0;

    while (curr != NULL)
    {
        if (!curr->used)
        {
            total_free += curr->size;
        }
        curr = curr->next;
    }
    return total_free;
}

my_free.h

#ifndef __MY_MALLOC_H
#define __MY_MALLOC_H

#ifdef __cplusplus
extern "C"
{
#endif

#include <stdint.h>
#include <stddef.h>
/*
 * 配置区域
 */
// 堆内存总大小 (例如 60KB,留点余量给栈)
#define MY_HEAP_SIZE (10 * 1024)

// 内存对齐 (STM32等32位处理器必须是4,64位处理器是8)
#define MEM_ALIGNMENT 4

    /* API 接口 */

    /**
     * @brief 初始化堆内存 (必须在系统启动时调用一次)
     */
    void mymem_init(void);

    /**
     * @brief 申请内存
     * @param size 需要的字节数
     * @return 指向内存的指针,失败返回 NULL
     */
    void *my_malloc(size_t size);

    /**
     * @brief 释放内存
     * @param ptr 之前申请的指针
     */
    void my_free(void *ptr);

    /**
     * @brief 获取剩余空闲内存大小 (用于监控)
     */
    uint32_t my_mem_get_free_size(void);

#ifdef __cplusplus
}
#endif

#endif /* __MY_MALLOC_H */




二、正点原子原子哥映射表法:


此代码是工作中的代码实现,实现方式是在预分配内存池的分配与释放,看懂以后发现该代码简单易懂,用起来也非常方便!

 

✅ 优点:
  1. 抗越界能力强(最大优势)

    • 你的管理数据(memmap)和用户数据(membase)是物理分离的。

    • 如果用户写数据越界了(Buffer Overflow),他只是弄脏了下一个数据块的数据,而不会破坏内存分配表

    • 而在链表法中,用户越界往往会覆盖掉下一个块的 Header,导致下一次 malloc 时程序直接崩溃(Hard Fault)。

  2. 没有碎片合并的烦恼

    • 释放速度极快,不需要像链表那样判断前后是否为空然后合并,只需要清零数组。

❌ 缺点:
  1. 内存浪费严重(内部碎片)

    • 假设 MEM_BLOCK_SIZE 是 32 字节。

    • 如果你 malloc(1)(申请1字节),它必须给你分配整整 32 字节。浪费率 97%

    • 对于大量小内存申请的场景(比如字符串处理),这种算法效率极低。

  2. 大内存申请效率低

    • 如果你要申请一大块内存,for 循环需要遍历 memmap 数组很长一段时间来寻找“连续的空闲块”,时间复杂度较高。

mymalloc.c

include "mymalloc.h"
#include "Common.h"
#include "ucos_ii.h"
#include <stdio.h>
#include "main.h"
#if USE_MQTT
//内存池(4字节对齐)
CCMRAM __align(4) u8 membase[MEM_MAX_SIZE];													//内部SRAM内存池

// 内存管理表

CCMRAM u16 memmapbase[MEM_ALLOC_TABLE_SIZE]; // 内部SRAM内存池MAP
// 内存管理参数
u32 memtblsize = MEM_ALLOC_TABLE_SIZE; // 内存表大小,每个表项2字节(u16),9K内存池共576个表项
u32 memblksize = MEM_BLOCK_SIZE; // 内存分块大小 , 每块16字节
u32 memsize = MEM_MAX_SIZE; // 内存总大小,最大空间9k
// 内存管理控制器
struct _m_mallco_dev mallco_dev = {
    my_mem_init, // 内存初始化
    my_mem_perused, // 内存使用率
    membase, // 内存池
    memmapbase, // 内存管理状态表
    0, // 内存管理未就绪
};

// 复制内存
// des:目的地址
// src:源地址
// n:需要复制的内存长度(字节为单位)
// 注意:支持内存重叠情况,从后往前复制
void mymemcpy(void* des, void* src, u32 n)
{
    u8* xdes = des; // 目的地址
    const u8* xsrc = src; // 源地址
    // 处理内存重叠情况:如果目标地址在源地址范围内,从后往前复制
    if((xdes > xsrc) && (xdes < xsrc + n)) {
        xdes = xdes + n - 1;
        xsrc = xsrc + n - 1;
        while (n--)
            *xdes-- = *xsrc--;
    } else {
        // 正常情况:从前往后复制
        while (n--)
            *xdes++ = *xsrc++;
    }
}

// 设置内存
// s:内存首地址
// c:要设置的值
// count:需要设置的内存大小(字节为单位)
void mymemset(void* s, u8 c, u32 count)
{
    u8* xs = s;
    while (count--)
        *xs++ = c;
}

// 内存管理初始化
// 功能:清空内存池和管理表,标记内存管理器为就绪状态
void my_mem_init(void)
{
    mymemset(mallco_dev.memmap, 0, memtblsize * 2); // 内存状态表数据清零
    mymemset(mallco_dev.membase, 0, memsize); // 内存池所有数据清零
    mallco_dev.memrdy = 1; // 内存管理初始化OK
}

// 获取内存使用率
// 返回值:使用率(0~100),表示已使用的内存块百分比
u8 my_mem_perused(void)
{
    u32 used = 0;
    u32 i;
    for (i = 0; i < memtblsize; i++) {
        if (mallco_dev.memmap[i])
            used++;
    }
    return (used * 100) / (memtblsize);
}


// 内存分配(内部调用)
// size:要分配的内存大小(字节)
// 返回值:0XFFFFFFFF,代表错误;其他,内存偏移地址
u32 my_mem_malloc(u32 size)
{
    signed long offset = 0;
    u16 nmemb; // 需要的内存块数
    u16 cmemb = 0; // 连续空内存块数
    u32 i;
    if (!mallco_dev.memrdy)
        mallco_dev.init(); // 未初始化,先执行初始化
    if (size == 0) {
        printf("my_malloc error: 分配内存大小为0\n");
        return 0XFFFFFFFF; // 不需要分配
    }
    nmemb = size / memblksize; // 获取需要分配的连续内存块数
    if (size % memblksize)
        nmemb++;
    // 检查整数溢出:nmemb不能超过u16最大值(65535),实际限制为内存池大小
    if (nmemb > memtblsize) {
        printf("my_malloc error: 申请内存过大,超过内存池大小\n");
        return 0XFFFFFFFF;
    }
    for (offset = memtblsize - 1; offset >= 0; offset--) // 搜索整个内存控制区
    {
        if (!mallco_dev.memmap[offset])
            cmemb++; // 连续空内存块数增加
        else
            cmemb = 0; // 连续内存块清零
        if (cmemb == nmemb) // 找到了连续nmemb个空内存块
        {
            for (i = 0; i < nmemb; i++) // 标注内存块非空
            {
                mallco_dev.memmap[offset + i] = nmemb;
            }
            return (offset * memblksize); // 返回偏移地址
        }
    }
    printf("my_malloc error: 内存不足\n");
    return 0XFFFFFFFF; // 未找到符合分配条件的内存块
}

// 释放内存(内部调用)
// offset:内存地址偏移
// 返回值:0,释放成功;1,未初始化;2,偏移超区;3,无效指针(不在内存池内)
u8 my_mem_free(u32 offset)
{
    int i;
    if (!mallco_dev.memrdy) // 未初始化,先执行初始化
    {
        mallco_dev.init();
        return 1; // 未初始化
    }
    if (offset >= memsize) // 偏移超出内存池范围
        return 2; // 偏移超区了
    if (offset % memblksize != 0) // 偏移必须是块大小的整数倍
        return 3; // 无效偏移地址
    
    int index = offset / memblksize; // 偏移所在内存块号码
    if (index >= memtblsize) // 索引超出管理表范围
        return 3; // 无效索引
    
    int nmemb = mallco_dev.memmap[index]; // 内存块数量
    if (nmemb == 0) // 该块未被分配
        return 3; // 重复释放或无效指针
    
    // 检查是否会越界
    if (index + nmemb > memtblsize)
        return 3; // 数据损坏,块数异常
    
    for (i = 0; i < nmemb; i++) // 内存块清零
    {
        mallco_dev.memmap[index + i] = 0;
    }
    return 0;
}

// 释放内存(外部调用)
// ptr:内存首地址
void myfree(void* ptr)
{
#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR cpu_sr;
#endif
    u32 offset;
    if (ptr == NULL)
        return; // 地址为0,直接返回
    
    // 验证指针是否在内存池范围内
    u32 ptr_addr = (u32)ptr;
    u32 base_addr = (u32)mallco_dev.membase;
    if (ptr_addr < base_addr || ptr_addr >= base_addr + memsize) {
        printf("myfree error: 指针不在内存池范围内 ptr=0x%08X base=0x%08X\n", ptr_addr, base_addr);
        return;
    }
    
    offset = ptr_addr - base_addr;
    OS_ENTER_CRITICAL();
    int ret = my_mem_free(offset); // 释放内存
    OS_EXIT_CRITICAL();
    if (ret != 0) {
        if (ret == 1)
            printf("myfree error: 内存管理器未初始化\n");
        else if (ret == 2)
            printf("myfree error: 指针超出内存池范围 ptr=0x%08X offset=0x%08X\n", ptr_addr, offset);
        else if (ret == 3)
            printf("myfree error: 无效指针或重复释放 ptr=0x%08X offset=0x%08X\n", ptr_addr, offset);
        else
            printf("myfree error: 未知错误码 %d\n", ret);
    }
}

// 分配内存(外部调用)
// size:内存大小(字节)
// 返回值:分配到的内存首地址,失败返回NULL
void* mymalloc(u32 size)
{

#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR cpu_sr;
#endif
    u32 offset;
    OS_ENTER_CRITICAL();
    offset = my_mem_malloc(size);
    OS_EXIT_CRITICAL();
    if (offset == 0XFFFFFFFF) {
        prtdbg("mymalloc error: 内存分配失败 \n");
        return NULL;
    } else
        return (void*)((u32)mallco_dev.membase + offset);
}

// 重新分配内存(外部调用)
// ptr:旧内存首地址
// size:要分配的内存大小(字节)
// 返回值:新分配到的内存首地址,失败返回NULL
void* myrealloc(void* ptr, u32 size)
{
#if OS_CRITICAL_METHOD == 3
    OS_CPU_SR cpu_sr;
#endif
    // 如果ptr为NULL,等同于malloc
    if (ptr == NULL)
        return mymalloc(size);
    
    // 验证旧指针是否在内存池范围内
    u32 ptr_addr = (u32)ptr;
    u32 base_addr = (u32)mallco_dev.membase;
    if (ptr_addr < base_addr || ptr_addr >= base_addr + memsize) {
        printf("myrealloc error: 旧指针不在内存池范围内 ptr=0x%08X\n", ptr_addr);
        return NULL;
    }
    
    u32 old_offset = ptr_addr - base_addr;
    u32 index = old_offset / memblksize;
    
    // 验证索引有效性
    if (index >= memtblsize || old_offset % memblksize != 0) {
        printf("myrealloc error: 无效的旧指针 offset=0x%08X index=%d\n", old_offset, index);
        return NULL;
    }
    
    OS_ENTER_CRITICAL();
    u32 old_nmemb = mallco_dev.memmap[index]; // 获取旧内存块数量
    OS_EXIT_CRITICAL();
    
    if (old_nmemb == 0) {
        printf("myrealloc error: 旧指针未被分配或已释放\n");
        return NULL;
    }
    
    u32 old_size = old_nmemb * memblksize; // 旧内存实际大小
    
    // 分配新内存
    OS_ENTER_CRITICAL();
    u32 new_offset = my_mem_malloc(size);
    OS_EXIT_CRITICAL();
    
    if (new_offset == 0XFFFFFFFF)
        return NULL;
    
    // 复制数据(取新旧大小的较小值)
    u32 copy_size = old_size < size ? old_size : size;
    mymemcpy((void*)(base_addr + new_offset), ptr, copy_size);
    
    // 释放旧内存
    myfree(ptr);
    
    return (void*)(base_addr + new_offset);
}

// 分配并清零内存(外部调用)
// nelem:元素个数
// elsize:每个元素的大小(字节)
// 返回值:分配到的内存首地址,失败返回NULL
// 注意:如果nelem或elsize为0,返回NULL(与标准库行为一致)
void* mycalloc(u32 nelem, u32 elsize)
{
    void* ptr;
    
    // 检查参数有效性:如果任一参数为0,返回NULL(与标准库calloc行为一致)
    if (nelem == 0 || elsize == 0)
        return NULL;
    
    // 检查乘法溢出
    u32 total_size = nelem * elsize;
    if (total_size / elsize != nelem) { // 乘法溢出检测
        printf("mycalloc error: 参数溢出 nelem=%d elsize=%d\n", nelem, elsize);
        return NULL;
    }

    ptr = mymalloc(total_size);
    if (ptr)
        mymemset(ptr, 0, total_size);

    return ptr;
}
/**
 * @brief 计算内存池中“最大连续空闲块”的字节数
 *
 * 工作原理:
 * - 顺序扫描内存管理表 `memmap`(每个元素对应一个内存块),0 表示空闲,非 0 表示占用
 * - 统计连续为 0 的块数的最长长度 `max`
 * - 返回值为 `max * memblksize`,即最大连续空闲空间的字节数
 *
 * 适用场景:
 * - 在进行一次性大字符串/大缓冲分配前(例如 JSON 打印)评估是否有足够的连续空间
 *
 * 注意事项:
 * - 未加互斥保护,仅用于观测;并发分配/释放时结果可能是近似值
 *
 * @return 最大连续空闲空间(字节数)
 */
u32 my_mem_max_free_block(void)
{
    u32 max = 0; // 当前扫描到的最大“连续空闲块”长度(以块计)
    u32 cur = 0; // 当前连续空闲块计数(以块计)
    u32 i;
    for (i = 0; i < memtblsize; i++) // 遍历内存映射表
    {
        if (mallco_dev.memmap[i] == 0) // 空闲块:推进当前连续计数
        {
            cur++;
            if (cur > max)
                max = cur;
        } else {
            cur = 0; // 遇到占用块:打断连续区,计数清零
        }
    }
    return max * memblksize; // 转换为字节数
}

#endif

mymalloc.h
 

#ifndef __MALLOC_H
#define __MALLOC_H
#include "stm32f2xx.h"
#include "main.h"
#if USE_MQTT
#ifndef NULL
#define NULL 0
#endif

#define MEM_BLOCK_SIZE			   16 
#if PROTOCOL_IS_NATIONCLR
#define MEM_MAX_SIZE			    10*1024 						        //最大管理内存 11K,
#else
#define MEM_MAX_SIZE			    9*1024  						        //最大管理内存 9K,
#endif
#define MEM_ALLOC_TABLE_SIZE	MEM_MAX_SIZE/MEM_BLOCK_SIZE //内存表大小
		 
//内存管理控制器
struct _m_mallco_dev
{
	void (*init)(void);					//初始化
	u8   (*perused)(void);		  	    	//内存使用率
	u8 	  *membase;					//内存池
	u16   *memmap; 					//内存管理状态表
	u8     memrdy; 						//内存管理是否就绪
};

extern struct _m_mallco_dev mallco_dev;	 //在mallco.c里面定义

void mymemset(void *s,u8 c,u32 count);	 //设置内存
void mymemcpy(void *des,void *src,u32 n);//复制内存 

void my_mem_init(void);					 //内存管理初始化函数
u32 my_mem_malloc(u32 size);		 //内存分配
u8 my_mem_free(u32 offset);		 //内存释放
u8 my_mem_perused(void);				 //获得内存使用率 
////////////////////////////////////////////////////////////////////////////////
//用户调用函数
void myfree(void *ptr);  			//内存释放
void *mymalloc(u32 size);			//内存分配
void *myrealloc(void *ptr,u32 size);//重新分配内存
void* mycalloc (u32 nelem, u32 elsize);
u32 my_mem_max_free_block(void);

#endif














#endif

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐