Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >实用算法系列之RT-Thread链表堆管理器

实用算法系列之RT-Thread链表堆管理器

原创
作者头像
逸珺
修改于 2020-06-08 02:37:10
修改于 2020-06-08 02:37:10
80200
代码可运行
举报
运行总次数:0
代码可运行

[导读] 前文描述了栈的基本概念,本文来聊聊堆是怎么会事儿。RT-Thread 在社区广受欢迎,阅读了其内核代码,实现了堆的管理,代码设计很清晰,可读性很好。故一方面了解RT-Thread内核实现,一方面可以弄清楚其堆的内部实现。将学习体会记录分享,希望对于堆的理解及实现有一个更深入的认知。

注,文中代码分析基于rt-thread-v4.0.2 版本。

什么是堆?

C语言堆是由malloc(),calloc(),realloc()等函数动态获取内存的一种机制。使用完成后,由程序员调用free()等函数进行释放。使用时,需要包含stdlib.h头文件。 C++预言的堆管理则是使用new操作符向堆管理器申请动态内存分配,使用delete操作符将使用完毕内存的释放给堆管理器。 注:本文只描述C的堆管理器实现相关内容。

以C语言为例,将上面的描述,翻译成一个图:

要动态管理一片内存,且需要动态分配释放,这样一个需求。很显然C语言需要将动态内存区抽象描述起来并实现动态管理。事实上,C语言中堆管理器其本质是利用数据结构将堆区抽象描述,所需要描述的方面:

  • 可用于分配的内存
  • 正在使用的内存块
  • 释放掉的内存块

再利用相应算法对于这类数据结构对象进行动态管理而实现的堆管理器。

经常看到各种算法书很多只讲算法原理,而不讲应用实例,往往体会不深。私以为可以做些改善。学而不能致用,何必费力去学。所以不是晦涩难懂的算法无用,而是没有去真正结合应用。可以再进一步想,如果算法没有应用场景,也一定会在技术发展的历程中逐渐被世人遗忘。所以建议学习阅读算法书籍时,找些实例来看看,一定会加深对算法的理解领悟。这是比较重要的题外话,送给大家以共勉。

所以从本质上讲,堆管理器就是数据结构+算法实现的动态内存管理器,管理内存的动态分配以及释放。

为什么要堆?

C编程语言对内存管理方式有静态,自动或动态三种方式。 静态内存分配的变量通常与程序的可执行代码一起分配在主存储器中,并在程序的整个生命周期内有效。 自动分配内存的变量在栈上分配,并随着函数的调用和返回而申请或释放。 对于静态分配内存和自动分配内存的生命周期,分配的大小必须是编译时常量(可变长度自动数组[5]除外)。 如果所需的内存大小直到运行时才知道(例如,如果要从用户或磁盘文件中读取任意大小的数据),则使用固定大小的数据对象则满足不了要求了。试想,即便假定都知道要多大内存,如在windows/Linux下有那么多应用程序,每个应用程序加载时都将运行中所需的内存采样静态分配策略,则如多个程序运行内存将很快耗尽。

分配的内存的生命周期也可能引起关注。 静态或自动分配都不能满足所有情况。 自动分配内存不能在多个函数调用之间保留,而静态数据在程序的整个生命周期中必然保留,无论是否真正需要(所以都采用这样的策略必然造成浪费)。 在许多情况下,程序员在管理分配的内存的生命周期具有更多的灵活性。

通过使用动态内存分配则避免了这些限制/缺点,在动态内存分配中,更明确(但更灵活)地管理内存,通常是通过从免费存储区(非正式地称为“堆”)中分配内存(为此目的而构造的内存区域)进行分配的。 在C语言中,库函数malloc用于在堆上分配一个内存块。 程序通过malloc返回的指针访问该内存块。 当不再需要内存时,会将指针传递给free,从而释放内存,以便可以将其用于其他目的。

谁实现堆

如果一问道这个问题,马上会说C编译器。不错C编译器实现了堆管理器,而事实上并非编译器在编译的过程中实现动态内存管理器,而是C编译器所实现的C库实现了堆管理器,比如ANSI C,VC, IAR C编译器,GNU C等其实都需要一些C库的支持,那么这些库的内部就隐藏了这么一个堆管理器。眼见为实吧,还是以IAR ARM 8.40.1 为例,其堆管理器就实现在:

.\IAR Systems\Embedded Workbench 8.3\arm\src\lib\dlib\heap

一看有这么多的源码,那么对于应用开发而言,有哪些选项需要进行配置呢?

支持四个选项:

  • Automatic:
    • 如果您的应用程序中有对堆内存分配例程的调用,但没有对堆释放例程的调用,则链接程序将自动选择无空闲堆。
    • 如果您的应用程序中有对堆内存分配例程的调用,则链接程序会自动选择高级堆。
    • 例如,如果在库中调用了堆内存分配例程,则链接程序会自动选择基本堆。
  • Advanced heap:高级堆(--advanced_heap)为广泛使用该堆的应用程序提供有效的内存管理。 特别是,重复分配和释放内存的应用程序可能会在空间和时间上获得较少的开销。 高级堆的代码明显大于基本堆的代码。
  • Basic heap: 基本堆(--basic_heap)是一个简单的堆分配器,适用于不经常使用堆的应用程序。 特别是,它可以用于仅分配堆内存而从不释放堆内存的应用程序中。 基本堆并不是特别快,并且在反复释放内存的应用程序中使用它很可能导致不必要的堆碎片化。 基本堆的代码远小于高级堆的大小。
  • No-free heap:无可用堆(--no_free_heap)使用此选项可以使用最小的堆实现。 因为此堆不支持释放或重新分配,所以它仅适用于在启动阶段为各种缓冲区分配堆内存的应用程序,以及永不释放内存的应用程序。

但是如果认为仅仅标准C库负责实现堆管理器,则这种理解并不全面。回到事物的本质,堆管理器是利用数据结构及算法动态管理一片内存的分配与释放。那么有这样需求的地方,都可能需要实现一个堆管理器。

堆管理器的实现很大程度取决于操作系统以及硬件体系架构。大体上需要实现堆内存管理器的有两大类:

  • 应用程序,应用程序需要堆内存管理器,是显而易见的。比如常见的windows/Linux下的应用程序,都需要堆内存管理器。而上述的cortex M或者其他单片机程序使用C/C++编程时都需要堆内存管理器。
  • 操作系统内核,操作系统内核需要像应用程序一样分配内存。 但是,内核中malloc的实现通常与C库使用的实现有很大不同。 例如,内存缓冲区可能需要符合DMA施加的特殊限制,或者可能从中断上下文中调用内存分配功能。这需要与操作系统内核的虚拟内存子系统紧密集成的malloc实现。比如Linux内核就需要实现内核版本的堆管理器,对外提供kmalloc/vmalloc申请内存,kfree/vfree用于释放内存。

怎么实现堆

对于RT-Thread的内核而言,也实现了一个内核堆管理器,这里就来梳理一下RT-Thread内核版本的小堆管理器的实现,同时来了解一下链表数据结构及算法操作的实例应用。

其堆管理器实现位于.\rt-thread-v4.0.2\rt-thread\src下mem.c,memheap.c以及mempool.c。

关键数据结构

其堆管理器主要的数据结构为heap_mem。

  • heap_mem

堆管理器初始化

堆管理器的初始化入口在mem.c,函数为:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void rt_system_heap_init(void *begin_addr, void *end_addr)
{
    struct heap_mem *mem;
    /*按4字节对齐转换地址*/
    /*如0x2000 0001~0x2000 0003,转后为0x2000 0004*/
    rt_ubase_t begin_align = RT_ALIGN((rt_ubase_t)begin_addr, RT_ALIGN_SIZE);
    /*如0x3000 0001~0x3000 0003,转后为0x3000 0000*/
    rt_ubase_t end_align   = RT_ALIGN_DOWN((rt_ubase_t)end_addr, RT_ALIGN_SIZE);
    
    /*调试信息,函数不可用于中断内部*/
    RT_DEBUG_NOT_IN_INTERRUPT;/* 分配地址范围至少能存储两个heap_mem */
    if ((end_align > (2 * SIZEOF_STRUCT_MEM)) &&
        ((end_align - 2 * SIZEOF_STRUCT_MEM) >= begin_align))
    {
        /* 计算可用堆区,4字节对齐 */
        mem_size_aligned = end_align - begin_align - 2 * SIZEOF_STRUCT_MEM;
    }
    else
    {
        rt_kprintf("mem init, error begin address 0x%x, and end address 0x%x\n",
                   (rt_ubase_t)begin_addr, (rt_ubase_t)end_addr);return;
    }/* heap_ptr指向堆区起始地址 */
    heap_ptr = (rt_uint8_t *)begin_align;RT_DEBUG_LOG(RT_DEBUG_MEM, ("mem init, heap begin address 0x%x, size %d\n",
                                (rt_ubase_t)heap_ptr, mem_size_aligned));/* 初始化堆起始描述符 */
    mem        = (struct heap_mem *)heap_ptr;
    mem->magic = HEAP_MAGIC;
    mem->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    mem->prev  = 0;
    mem->used  = 0;
#ifdef RT_USING_MEMTRACE
    rt_mem_setname(mem, "INIT");
#endif
​
    /* 初始化堆结束描述符 */
    heap_end        = (struct heap_mem *)&heap_ptr[mem->next];
    heap_end->magic = HEAP_MAGIC;
    heap_end->used  = 1;
    heap_end->next  = mem_size_aligned + SIZEOF_STRUCT_MEM;
    heap_end->prev  = mem_size_aligned + SIZEOF_STRUCT_MEM;
#ifdef RT_USING_MEMTRACE
    rt_mem_setname(heap_end, "INIT");
#endif
​
    rt_sem_init(&heap_sem, "heap", 1, RT_IPC_FLAG_FIFO);/* 初始化释放指针指向堆的开始 */
    lfree = (struct heap_mem *)heap_ptr;
}

传入链接堆区的内存起始地址,以及结束地址。以STM32为例,传入0x20000000--0x20018000,96k字节

上述rt_system_heap_init( 0x20000000,0x20018000),主要做了下图这么一件事情。

将堆管理头尾描述符进行了初始化,并指向对应的内存地址。用图翻译一下:

技巧点:

  • 利用类型强制转换将内存数据转换为struct heap_mem *。实现了静态双链表的创建
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mem      = (struct heap_mem *)heap_ptr;
heap_end = (struct heap_mem *)&heap_ptr[mem->next];
  • 定义heap_mem没有定义使用多少字节为该块的用户数据字节数,节约了内存。是一个比较好的处理方式。
  • 对齐方式可配置,RT_ALIGN_SIZE默认为4字节。

向堆申请内存

用户调用rt_malloc 用于申请分配动态内存。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void *rt_malloc(rt_size_t size)
{
    rt_size_t ptr, ptr2;
    struct heap_mem *mem, *mem2;if (size == 0)
        return RT_NULL;RT_DEBUG_NOT_IN_INTERRUPT;
    /*按四字节对齐申请,如申请5字节,则实际按8字节申请*/
    if (size != RT_ALIGN(size, RT_ALIGN_SIZE))
        RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d, but align to %d\n",
                                    size, RT_ALIGN(size, RT_ALIGN_SIZE)));
    else
        RT_DEBUG_LOG(RT_DEBUG_MEM, ("malloc size %d\n", size));/* 按四字节对齐申请,如申请5字节,则实际按8字节申请 */
    size = RT_ALIGN(size, RT_ALIGN_SIZE);if (size > mem_size_aligned)
    {
        RT_DEBUG_LOG(RT_DEBUG_MEM, ("no memory\n"));
        return RT_NULL;
    }/* 每块的长度必须至少为MIN_SIZE_ALIGNED=12 STM32*/
    if (size < MIN_SIZE_ALIGNED)
        size = MIN_SIZE_ALIGNED;/* 获取堆保护信号量 */
    rt_sem_take(&heap_sem, RT_WAITING_FOREVER);for (ptr = (rt_uint8_t *)lfree - heap_ptr;
         ptr < mem_size_aligned - size;
         ptr = ((struct heap_mem *)&heap_ptr[ptr])->next)
    {
        mem = (struct heap_mem *)&heap_ptr[ptr];/*如果该块未使用,且满足大小要求*/
        if ((!mem->used) && (mem->next - (ptr + SIZEOF_STRUCT_MEM)) >= size)
        {
            /* mem没有被使用,至少完美的配合是可能的:
             * mem->next - (ptr + SIZEOF_STRUCT_MEM) 计算出mem的“用户数据大小” */
            if (mem->next - (ptr + SIZEOF_STRUCT_MEM) >=
                (size + SIZEOF_STRUCT_MEM + MIN_SIZE_ALIGNED))
            {
                /* (除了上面的,我们测试另一个结构heap_mem (SIZEOF_STRUCT_MEM)
                 * 是否包含至少MIN_SIZE_ALIGNED的数据也适合'mem'的'用户数据空间')
                 * -> 分割大的块,创建空的余数,
                 * 余数必须足够大,以包含MIN_SIZE_ALIGNED大小数据:
                 * 如果mem->next - (ptr + (2*SIZEOF_STRUCT_MEM)) == size,
                 * struct heap_mem 会适合,在mem2及mem2->next没有使用
                 */
                ptr2 = ptr + SIZEOF_STRUCT_MEM + size;/* create mem2 struct */
                mem2       = (struct heap_mem *)&heap_ptr[ptr2];
                mem2->magic = HEAP_MAGIC;
                mem2->used = 0;
                mem2->next = mem->next;
                mem2->prev = ptr;
#ifdef RT_USING_MEMTRACE
                rt_mem_setname(mem2, "    ");
#endif
                /*将ptr2插入mem及mem->next之间 */
                mem->next = ptr2;
                mem->used = 1;if (mem2->next != mem_size_aligned + SIZEOF_STRUCT_MEM)
                {
                    ((struct heap_mem *)&heap_ptr[mem2->next])->prev = ptr2;
                }
#ifdef RT_MEM_STATS
                used_mem += (size + SIZEOF_STRUCT_MEM);
                if (max_mem < used_mem)
                    max_mem = used_mem;
#endif
            }
            else
            {
                mem->used = 1;
#ifdef RT_MEM_STATS
                used_mem += mem->next - ((rt_uint8_t *)mem - heap_ptr);
                if (max_mem < used_mem)
                    max_mem = used_mem;
#endif
            }
            /* 设置块幻数 */
            mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE
            if (rt_thread_self())
                rt_mem_setname(mem, rt_thread_self()->name);
            else
                rt_mem_setname(mem, "NONE");
#endif
​
            if (mem == lfree)
            {
                /* 寻找下一个空闲块并更新lfree指针*/
                while (lfree->used && lfree != heap_end)
                    lfree = (struct heap_mem *)&heap_ptr[lfree->next];RT_ASSERT(((lfree == heap_end) || (!lfree->used)));
            }rt_sem_release(&heap_sem);
            RT_ASSERT((rt_ubase_t)mem + SIZEOF_STRUCT_MEM + size <= (rt_ubase_t)heap_end);
            RT_ASSERT((rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM) % RT_ALIGN_SIZE == 0);
            RT_ASSERT((((rt_ubase_t)mem) & (RT_ALIGN_SIZE - 1)) == 0);RT_DEBUG_LOG(RT_DEBUG_MEM,
                         ("allocate memory at 0x%x, size: %d\n",
                          (rt_ubase_t)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM),
                          (rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));RT_OBJECT_HOOK_CALL(rt_malloc_hook,
                                (((void *)((rt_uint8_t *)mem + SIZEOF_STRUCT_MEM)), size));/* 返回除mem结构之外的内存地址 */
            return (rt_uint8_t *)mem + SIZEOF_STRUCT_MEM;
        }
    }
    /* 释放堆保护信号量 */
    rt_sem_release(&heap_sem);return RT_NULL;
}

其基本思路,从空闲块链表开始检索内存块,如检索到某块空闲且满足申请大小且其剩余空间至少能存储描述符,则满足了申请要求,则将后续内存头部生成描述,更新前后指针,标记幻数以及块已被使用标记,将该块插入链表。返回申请成功的内存地址。如果检索不到,则返回空指针,表示申请失败,堆目前没有满足要求的内存可供使用。实际上,上述代码在运行时将堆内存区按照下述示意图进行动态维护。

概括一下:

  • heap_ptr总是指向堆起始地址,heap_end总是指向最后一个块,两者配合可以实现边界保护,在释放内存时使用。
  • lfree 总是指向最地址最小的空闲块,因此在动态申请内存时,总是从该块进行检索是否有满足申请要求的内存块可供使用。
  • used=1表示该块被占用,非空闲。used=0表示该块空闲。
  • magic 字段幻数,起始就是一个特殊标记字,与used=0配合,用于检测异常,试想一下如果仅仅用used=0判断块是空闲,则易出错,或者需要加其他的辅助代码,才能保证代码的健壮性。
  • 动态内存管理申请比较慢,需要检索链表,以及额外的内存开销。
  • rt_realloc 及rt_calloc 不做分析了

释放内存

释放内存由rt_free实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void rt_free(void *rmem)
{
    struct heap_mem *mem;if (rmem == RT_NULL)
        return;RT_DEBUG_NOT_IN_INTERRUPT;RT_ASSERT((((rt_ubase_t)rmem) & (RT_ALIGN_SIZE - 1)) == 0);
    RT_ASSERT((rt_uint8_t *)rmem >= (rt_uint8_t *)heap_ptr &&
              (rt_uint8_t *)rmem < (rt_uint8_t *)heap_end);RT_OBJECT_HOOK_CALL(rt_free_hook, (rmem));
    /* 申请释放地址不在堆区 */
    if ((rt_uint8_t *)rmem < (rt_uint8_t *)heap_ptr ||
        (rt_uint8_t *)rmem >= (rt_uint8_t *)heap_end)
    {
        RT_DEBUG_LOG(RT_DEBUG_MEM, ("illegal memory\n"));return;
    }/* 获取块描述符 */
    mem = (struct heap_mem *)((rt_uint8_t *)rmem - SIZEOF_STRUCT_MEM);RT_DEBUG_LOG(RT_DEBUG_MEM,
                 ("release memory 0x%x, size: %d\n",
                  (rt_ubase_t)rmem,
                  (rt_ubase_t)(mem->next - ((rt_uint8_t *)mem - heap_ptr))));
​
​
    /* 获取堆保护信号量 */
    rt_sem_take(&heap_sem, RT_WAITING_FOREVER);/* 待释放的内存,其块描述符需是使用状态 */
    if (!mem->used || mem->magic != HEAP_MAGIC)
    {
        rt_kprintf("to free a bad data block:\n");
        rt_kprintf("mem: 0x%08x, used flag: %d, magic code: 0x%04x\n", mem, mem->used, mem->magic);
    }
    RT_ASSERT(mem->used);
    RT_ASSERT(mem->magic == HEAP_MAGIC);
    /* 清除使用标志 */
    mem->used  = 0;
    mem->magic = HEAP_MAGIC;
#ifdef RT_USING_MEMTRACE
    rt_mem_setname(mem, "    ");
#endif
​
    if (mem < lfree)
    {
        /* 更新空闲块lfree指针 */
        lfree = mem;
    }
​
#ifdef RT_MEM_STATS
    used_mem -= (mem->next - ((rt_uint8_t *)mem - heap_ptr));
#endif
​
    /* 如临近块也处于空闲态,则合并整理成一个更大的块 */
    plug_holes(mem);
    rt_sem_release(&heap_sem);
}
RTM_EXPORT(rt_free);

合并空闲块plug_holes

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
static void plug_holes(struct heap_mem *mem)
{
    struct heap_mem *nmem;
    struct heap_mem *pmem;RT_ASSERT((rt_uint8_t *)mem >= heap_ptr);
    RT_ASSERT((rt_uint8_t *)mem < (rt_uint8_t *)heap_end);
    RT_ASSERT(mem->used == 0);/* 前向整理 */
    nmem = (struct heap_mem *)&heap_ptr[mem->next];
    if (mem != nmem &&
        nmem->used == 0 &&
        (rt_uint8_t *)nmem != (rt_uint8_t *)heap_end)
    {
        /*如果mem->next是空闲,且非尾节点,则合并*/
        if (lfree == nmem)
        {
            lfree = mem;
        }
        mem->next = nmem->next;
        ((struct heap_mem *)&heap_ptr[nmem->next])->prev = (rt_uint8_t *)mem - heap_ptr;
    }/* 后向整理 */
    pmem = (struct heap_mem *)&heap_ptr[mem->prev];
    if (pmem != mem && pmem->used == 0)
    {
        /* 如mem->prev空闲,将mem与mem->prev合并 */
        if (lfree == mem)
        {
            lfree = pmem;
        }
        pmem->next = mem->next;
        ((struct heap_mem *)&heap_ptr[mem->next])->prev = (rt_uint8_t *)pmem - heap_ptr;
    }
}

动态内存的释放相对比较简单,其思路主要是判断传入地址是否在堆区,如是堆内存,则判断其块信息是否合法。如果合法,则将使用标志清除。同时如果临近块如果是空闲态,则利用plug_holes将空闲块进行合并,合并成一个大的空闲块。

内存泄漏

使用free释放内存失败会导致不可重用内存的累积,程序不再使用这些内存。这将浪费内存资源,并可能在耗尽这些资源时导致分配失败。

怎么使用堆

堆区的配置

对于STM32而言,位于board.h

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/ * 配置堆区大小,可根据实际使用进行修改 */
#define HEAP_BEGIN   STM32_SRAM1_START
#define HEAP_END     STM32_SRAM1_END/* 用于板级初始化堆区 */
void rt_system_heap_init(void *begin_addr, void *end_addr)

堆的接口函数

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
用于动态申请内存
void *rt_malloc(rt_size_t size)
/*追加申请内存,此函数将更改先前分配的内存块。*/
void *rt_realloc(void *rmem, rt_size_t newsize)
/* 申请的内存被初始化为0 */
void *rt_calloc(rt_size_t count, rt_size_t size)

内存分配不能保证成功,而是可能返回一个空指针。使用返回的值,而不检查分配是否成功,将调用未定义的行为。这通常会导致崩溃,但不能保证会发生崩溃,因此依赖于它也会导致问题。

对于申请的内存,使用前必须进行返回值判断,否则申请失败,且任继续使用。将会出现意想不到的错误!!

总结一下

通过对RT-Thread的小堆管理器实现的梳理,层层递进更深入理解以下一些要点:

  • 为什么需要堆,为什么堆是C/C++运行时的基础之一。堆可实现动态内存管理的多样性,在牺牲一定开销情况下(申请/释放开销,以及内存开销),可以提供内存的利用率,在一定程度上解决内存不足的需求。
  • 可以更深入的理解链表实用价值,理解静态实现方法的一些技巧。
  • 通过更深入的理解堆的实现,可以更好的使用堆。
  • 理解堆管理器究竟在哪里实现的,C/C++标准库,以及操作系统内核都可能实现堆管理器。
  • RT-Thread的小堆实现是一个比较简单和比较好的学习堆管理的例子,事实上堆的实现还有更复杂的场景,比如基于SLAB堆管理器实现,以及IAR中库的堆实现还需要使用树这个数据结构。

堆使用常见错误

  • 使用前没有检查分配失败:内存分配不能保证成功,不成功时返回一个空指针。使用返回的空指针,而直接操作这个空指针。可能会导致程序崩溃。
  • 内存泄露:使用free释放内存也可能会失败,失败会导致不可重用内存的累积,这些内存将在堆区不再能被使用。这将浪费内存资源,并可能会随着程序的运行耗尽所有堆内存。
  • 逻辑错误:所有的分配须使用相同的模式:使用malloc申请分配内存,使用free释放内存。如果使用后而不释放。例如在调用free释放之后或在调用malloc之前使用内存、也或者两次调用free释放内存(“double free”)等,通常可能会导致段错误并导致程序崩溃。这些错误可能是偶发的,而且很难调试发现。

本文分享自微信公众号 -嵌入式客栈(embInn),作者:逸珺,严禁商用,违法必究,更多更新内容请关注

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
rt-thread的内存管理分析
内存是计算机中十分重要的资源。随着芯片性能的提升,容量的变大,内存资源的管理显得非常重要。内存管理是操作系统中一个基本功能,一般操作系统的功能可以概括为五个部分:处理器管理、内存管理、任务管理、I/O设备管理、文件管理。对于嵌入式操作系统,一个好的内存管理策略,将大大提高系统的性能,对系统稳定性也至关重要。
bigmagic
2020/07/17
1.7K0
【玩转 RT-Thread】线程管理原理
在日常生活中,我们通常会将一个大的问题拆分细化,拆开成若干个小问题,通过逐个解决小问题,大问题也就解决了。 同样的在RT-Thread多线程操作系统中,开发人员基于这种分而治之的思想,将一个复杂的应用问题抽象成若干个小的、可调度的、可序列化的程序单元。当合理地划分任务并正确地执行时,这种设计能够让系统满足实时系统的性能及时间的要求。
攻城狮杰森
2022/06/03
6440
【玩转 RT-Thread】线程管理原理
OpenHamrony 轻内核M核源码分析系列七 动态内存Dynamic Memory
内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。
小帅聊鸿蒙
2025/05/22
690
OpenHamrony 轻内核M核源码分析系列七 动态内存Dynamic Memory
STM32标准库移植RT-Thread Nano添加FinSH与控制台[通俗易懂]
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/143606.html原文链接:https://javaforall.c
全栈程序员站长
2022/08/26
7380
STM32标准库移植RT-Thread Nano添加FinSH与控制台[通俗易懂]
OpenHarmony 内核源码分析 (内存池管理) | 如何高效切割合并内存块
为了便于理解源码,站长画了以下图,图中列出主要结构体,位图,分配和释放信息,逐一说明。
小帅聊鸿蒙
2025/04/07
1030
OpenHarmony 内核源码分析 (内存池管理) | 如何高效切割合并内存块
esp-idf的内存管理——tlsf算法
早几年(2019年之前)idf使用的堆管理器实现很简单,使用一个链表将所有(空闲和非空闲)的内存块串起来。对此,已经有同学分析过,可以参考这篇博客:esp32 heap 内存管理简析。下面这张图也是来自这篇博客,一目了然:
全栈程序员站长
2022/07/18
2.5K1
esp-idf的内存管理——tlsf算法
php内存管理
程序是代码和数据的集合,进程是运行着的程序;操作系统需要为进程分配内存;进程运行完毕需要释放内存;内存管理就是内存的分配和释放;
PHP开发工程师
2021/04/17
2.3K0
php内存管理
从uClibc部分源码总结固件利用思路的变化
审计固件的时候碰到了一个mips64下uClibc堆管理利用的问题,恰巧网络上关于这个的分析不是很多,于是研究了一下。并不是很全面,做个索引,若有进一步了解时继续补全。
赤道企鹅
2022/08/01
7450
RT-Thread 中的多线程
RT-Thread 线程管理的主要功能是对线程进行管理和调度,系统中总共存在两类线程,分别是系统线程和用户线程,系统线程是由 RT-Thread 内核创建的线程,用户线程是由应用程序创建的线程,这两类线程都会从内核对象容器中分配线程对象,当线程被删除时,也会被从对象容器中删除,如下方图所示,每个线程都有重要的属性,如线程控制块、线程栈、入口函数等。
苏州程序大白
2022/04/14
7000
RT-Thread 中的多线程
RT-Thread Nano如何适配I2C设备API,并在RT-Thread Nano使用软件包
本文介绍了如何在 RT-Thread Studio 上使用 RT-Thread Nano,并基于 BearPI-IOT STM32L431RCT6 的基础工程进行讲解如何使用 I2C 设备接口及相关软件包使用。
Rice加饭
2022/05/10
9750
RT-Thread Nano如何适配I2C设备API,并在RT-Thread Nano使用软件包
MacOS系统上的堆介绍及利用
0CTF / TCTF2019比赛时出了一道MacOS下的堆利用题目,但没找到文章介绍MacOS上的内存管理及攻击方式,这里以该题为例,简单分享一下之前总结的一些MacOS系统的堆管理及利用思路。
ChaMd5安全团队
2019/05/07
2.2K0
MacOS系统上的堆介绍及利用
DPDK 内存管理---malloc_heap和malloc_elem
博文是基于dpdk20.5代码阅读所写,如理解有错误或不当之处,烦请指正,不甚感激。也可以私信我一起探讨。
dpdk-vpp源码解读
2023/01/04
1.5K0
DPDK 内存管理---malloc_heap和malloc_elem
【RT-Thread笔记】内核对象模型
RT-Thread包括了很多不同类型的对象,如线程,信号量,互斥量等。在代码中,这些对象被汇总到一个枚举中(在rtdef.h中):
正念君
2019/11/28
7250
【RT-Thread笔记】内核对象模型
c语言线程间传递消息,线程间通信[通俗易懂]
前面一章讲了线程间同步,提到了信号量、互斥量、事件集等概念;本章接着上一章的内容,讲解线程间通信。在裸机编程中,经常会使用全局变量进行功能间的通信,如某些功能可能由于一些操作而改变全局变量的值,另一个功能对此全局变量进行读取,根据读取到的全局变量值执行相应的动作,达到通信协作的目的。RT-Thread 中则提供了更多的工具帮助在不同的线程中间传递信息,本章会详细介绍这些工具。学习完本章,大家将学会如何将邮箱、消息队列、信号用于线程间的通信。
全栈程序员站长
2022/11/08
2.6K0
c语言线程间传递消息,线程间通信[通俗易懂]
裸机内存管理解析
在计算机系统中,变量、中间数据一般存放在系统存储空间中,只有实际使用的时候才将他们从存储空间调入到中央处理器内部进行计算。通常存储空间分为两类:内部存储空间和外部存储空间。对于电脑来讲,内部存储空间就是电脑的内存,外部存储空间就是电脑的硬盘。而对于单片机来讲,内部存储就是 RAM ,随机存储器。外部存储可以理解为 flash ,掉电不丢失。该篇文章的主题,内存管理,主要讨论的是关于 RAM 的管理。
wenzid
2021/03/04
1K0
裸机内存管理解析
Memcache内存分配机制
memcached 默认情况下采用了 Slab Allocator 的机制分配和管理内存. 在该机制出现之前内存分配简单的通过 malloc 和 free 来管理所有的记录, 旧的方式会导致产生很多内存碎片, 加重机器管理内存的负担, 甚至有可能导致操作系统比 memcached 进程本身还慢, Slab Allocator 则解决了该问题.
tunsuy
2022/10/27
7910
RT-Thread的对象容器设计思想浅析
最近在学习RT-Thread操作系统的内核部分设计。RT-Thread的面向对象编程思想非常的巧妙,可以看我之前的写的文章。
bigmagic
2020/05/27
1.5K0
【c/c++】深入探秘:C++内存管理的机制
数据段就是我们所说的全局变量,代码段是我们所说的常量区,我们需要重点关注的是堆区,这部分是由我们自己控制的
用户11029103
2024/04/16
3310
【c/c++】深入探秘:C++内存管理的机制
C++初阶-C/C++内存管理
C/C++内存管理 零、前言 一、C/C++内存分布 二、C语言动态内存管理 三、C++动态内存管理 四、operator new与operator delete函数 1、operator new与operator delete函数 2、operator new与operator delete的类专属重载 五、new和delete的实现原理 1、内置类型 2、自定义类型 六、定位new表达式(placement-new) 七、常见面试题 1、malloc/free和new/delete的区别 2、内存泄漏
用户9645905
2022/11/30
4680
C++初阶-C/C++内存管理
【C++】动态内存管理
了解了这些之后,我们再来通过一个经典练习题深入理解一下内存区域的划分,如下代码:
修修修也
2024/04/20
1480
【C++】动态内存管理
相关推荐
rt-thread的内存管理分析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验