尽管malloc和free所提供的内存分配接口比之brk和sbrk要容易许多,但在使用时仍然容易犯下各种编程错误。
理解malloc和free的实现,将使我们洞悉产生这些错误的原因以及如何才能避免此类错误。
to be continued 。。。。。。
malloc的实现很简单。
它首先会扫描之前由free所释放的空闲内存列表,以求找到尺寸大于或等于要求的一块空闲内存。
(这取决于具体实现,采用的扫描策略会有所不同。例如first-fit或best-fit。)
如果这一内存块的尺寸正好与要求相当,就把它直接返回给调用者。如果是一块较大的内存,那么将对其进行分割,在将一块大小相同的内存返回给调用者的同时,把较小的那块空闲内存块保留在空闲列表中。
如果在空闲内存列表中根本找不到足够大的空闲内存块,那么malloc会调用sbrk以分配更多的内存。为减少对sbrk的调用次数,malloc并未只是严格按所需字节数来分配内存,而是以更大幅度(以虚拟内存页大小的数倍)来增加prgram break,并将超出部分置于空闲内存列表。
至于free函数的实现则更为有趣。
当free将内存块置于空闲列表之上使,是如何知晓内存块大小的?
这是通过一个小技巧来实现的。
当malloc分配内存块时,会额外分配几个字节来存放记录这块内存大小的整数值。该整数位于内存块 的起始处,而实际返回给调用者的内存地址恰好位于这一长度记录字节之后,如下图所示。
当将内存块置于空闲内存列表(双向链表)时,free会使用内存块本身的空间来存放链表指针,将自身添加到列表中,如下图
随着内存不断地释放和重新分配,空闲列表中的空闲内存和已分配的在用内存混杂在一起,如下图
应该认识到,C语言允许程序创建指向堆中任意位置的指针,并修改其指向的数据,包括由free和malloc维护的内存块长度、指向前一空闲块和后一空闲块的指针。辅之以之前的描述,一旦推究起隐晦难解的编程缺陷来,这无疑形同掉进了火药桶。
例如,假设经由一个错误指针,程序无意间增加了冠于一块已分配内存的长度值,并随即释放这块内存,free因之会在空闲列表记录下这块长度失真的内存。
随后,malloc也许会重新分配这块内存,从而导致如下场景:程序的两个指针分别指向两块它认为互不相干的已分配内存,但实际上这块内存缺相互重叠。
至于其他出错情况则数不胜数。
要避免这类错误,应该遵守一下原则。