内存泄漏令开发者头痛的地方也正是这个原因,内存泄漏的问题往往无法在第一时间被发现!而对于不熟悉内存管理的开发者更是难以定位错误。
对于动态内存的操作,需要时刻记住:当一块申请的内存不再使用的时候,必须及时释放。一个malloc操作需要对应一个free操作。
4、内存对齐
在很多的场合下,分配的内存不仅要满足申请的大小,也需要进行对齐才能够使分配的空间能够被转换成除char之外其他的结构类型。系统对内存的分配一般会以int型变量的字节数进行对齐。AWorks提供的aw_mem_align接口可以使用户获取自定义对齐的内存空间。
类型对齐相关的知识请读者自行查找相关资料,这里不再展开细讲。
内存管理算法
接下来我们学习一下怎么去对堆空间的内存进行管理。这里我们主要介绍嵌入式中两种常用的内存管理算法。
1、链表法
链表法维护着两个链表,两个链表分别记录着已分配的使用内存段和未分配的空闲内存段。当申请一片内存的时候,从空闲内存段中找到合适的块分配给用户,同时链入使用内存段。而释放的时候则从已使用的内存段找到相应的表,然后释放到空闲内存段中以供下次分配。
现在我们以最先匹配算法为例介绍算法的细节。最先匹配算法是从空闲链表的表头出发,依次寻找空闲的内存,一旦找到足够大的连续区域则将其返回给用户。
还记得前面说的,管理内存区域需要额外的记录信息,链表法一般是在操作内存空间的时候申请额外的空间记录相应的信息。我们假定下图红色部分记录着使用的内存区,青色记录空闲的内存区,这里使用free link链表维护空闲内存段,used link维护使用的内存段:
程序运行一段时间之后,假设堆内的空间分布如下:
空闲区和使用区的信息都被两个表维护着。
现在用户需要申请一片大小为3k的内存,系统会从free link出发,先是找到2k的空闲区,由于2k的空间不够用,接下来再继续寻找,找到了4k的区域,发现4k的区域够大了,就会将4k的空间取走3k的空间并将其链入used link。
尽管后面3k的空间更加适合分配,但是最先匹配算法一旦找到足够大的空间便不会继续往下寻找。
当用户用完资源的时候,把申请的3k还回去,系统会从used link找到申请的内存,将链入free link以供下次分配,然后将空闲相邻的内存块合并成完整的一块:
现在考虑这样的一种情况:假设用户要申请5k的内存块,系统能够提供吗?并不能。
虽然空闲的内存块一共有9k(2k+4k+3k),但是9k的内存并不连续,因此无法分配给用户。这就是外部内存碎片——虽然整个空间的空闲内存足够大,但却因为零碎的内存块割裂了连续内存而无法分配出去。
其他的链表法还有最佳匹配算法,下次匹配算法等.有兴趣的读者可以自行查找相关资料。
2、位图法
使用位图法,系统的内存会被划分成固定的内存块。再用变量的其中一位指示其中的一块内存:
图中的一个方格代表一块固定大小的内存块,这里假定1k。用一个16位的变量指代16k的内存段。如果一个块是空闲的,则用0表示,如果是被使用的,则用1表示。
下图的第1,2个内存块和第7,8,9个内存块都被使用了,而相应的位都被置1说明被占用了。
相比链表法,位图法采用更少的额外空间记录内存堆的信息,而且由于申请与释放都是整块的,会产生更少的外部碎片。
但是假如用户只申请几个字节的内存,但是却分配了1k的内存块,则大量的空间不会被使用,这样导致的无法使用的内存我们称为内部内存碎片。
减少内部内存碎片的其中一个方法是合理地选择内存块的大小,固定尺寸较小的内存块导致的内存碎片会更小——当用户申请几字节的内存,比起固定1k的内存块,固定16字节的内存块产生更少的碎片。但是固定尺寸变小了也会导致需要更多的位记录内存的信息。
现在有很多优秀的位图算法——将内存分成不同的固定大小获取更快的分配速度和更少的内存碎片,有兴趣的读者可自行查找相关资料。