freertos入门—堆的概念
堆就是一块空闲的内存。下面举个例子更好的理解堆的概念:
堆是一块空闲的内存,我们可以定义一个数组char heap_buf[1024],可以看到该数组就是一个空闲的内存,我们只需要在它上面实现内存的分配和释放那么它就一个堆。
char heap_buf[1024];
接下来我们实现一个简单的函数:
int pos = 0;
void *my_malloc(int size)
{int old_pos = pos;pos += size;return &heap_buf[old_pos];
}
我们定义的malloc函数比较简单,它做的就是在一个 buf 中分配一个小块的内存,它的下标是从0到1023,一开始pos = 0,我们想来分配size大小的空间,malloc函数首先将 old_pos 的地址返回,然后 pos 等于原来的位置加上 size。
当我们在空闲的内存上实现 malloc函数时,这块空闲的内存就被称为堆,我们使用一个整数表示空闲内存的位置,使用这种最简单的方式我们没有办法实现 free 函数。
我们可以在主函数中测试 malloc 函数,测试代码如下:
int main(void)
{int i;char *buf = my_malloc(100);for(i = 0; i < 26; i++)buf[i] = 'A' + i;
}
对代码进行调试,可以看到已经成功分配了 buf 以及在 buf 中填充数据。
那么上述的 malloc 函数不能有对应得 free 函数呢?一开始我们在堆中分配了100字节。
接下来我们想再次分配100字节。
若是我们调用 my_free(buf) ,my_free 函数根据 buf 的地址并不知道释放多大的内存。所以对于我们上述所写的简单的 malloc 函数,并没有办法实现对应的 free 函数,那么一般的堆管理函数又是如何实现堆的释放的呢?
比如说,我们要执行 char *buf = my_malloc(100); 它并不是分配100字节的空间,而是分配一个头部加上100字节的空间给我们,但是它返回的时候是返回头部之后实际能存储数据的位置。在这个头部中就有保留有分配大小,若是我们以后想要调用 free(buf),free 函数会根据 buf 的地址找到头部,在头部中读取分配的数值,就知道了需要释放的内存有多大。一般的堆管理都是这样的,它并不是简单的分配100字节的内存,它还会分配一个头部。
接下来对堆管理函数进行扩展讲解一下,假设在一块内存上我们分配了若干次:
若是我们想要释放 buf2 ,那么这一块区域则变为空闲的内存,之前遗留下来的内存也是空闲的,那么我们怎样才能管理这些隔离开的空闲区域呢,这必定会引入链表。
这个链表我们如何表示呢?我们可以定义一个struct head :
struct head
{int size;struct head *next_free;
}
有了这个链表头后,我们就可以管理这些空闲的 buf 了,比如说我们定义一个全局变量 struct head g_heap,初始情况下我们如下1024字节的内存,那么链表头 g_heap 则为:
一开始我们的全局变量的 size 为0,但是它会指向下一个头部。下一个头部的 size 等于1024,表示从这个头部开始,空闲的大小是1024;它的 next_free = NULL,表示没有下一个块了。
若是我们想分配100字节的内存,分配内存时会从 g_heap.next_free 中找到下一个空闲的内存,下一个空闲内存的大小为1024,我们需要从这一块空间中挖出100字节以及头部,发现1024大于108则进行拆分,拆分之后就得到两个部分:分配部分和空闲部分。在空闲部分的头部还会构造一个信息,size = 1024-108以及next_heap = null。此外,全局变量的 size = 0,next_free也发生变换,如下所示:
假设我们再来分配50字节内存,我们先从全局链表中找到 next_free ,发现它的大小是超过58的,那我们继续分配,分配结果如下所示:
分配完成后,我们若是想释放 buf ,释放时根据 buf 找到头部,头部里面表示这块空间是100字节,那么加上头部就是108字节,我们若是想要释放这块空间就需要将这块空闲的空间加入到这个链表中,如下图所示,空闲空间的 next_free 指向释放的空间。
以后我们想要申请内存的时候,从 g_heap 中找到第一个块,若是第一个块内存不够的话,我们就可以从这个链表这里找到第二个块,第二个块够的话进行分配,不够的话失败。
堆的管理还有很多种实现,我们只是演示了一种最常见的做法。