C++的内存配置基本操作是 ::operator new(),内存释放的基本操作是 ::operator delete()。这两个全局函数相当于C的malloc()和free()函数。是的,正是如此,STL正是以malloc()和free()完成内存的配置与释放。
但是考虑到小型区块所可能造成的内存破碎问题,STL中设计了双层级配置器, 第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不用的策略:当配置区块超过128bytes时,视之为“足够大”,便调用第一级配置器;当配置区块小于128bytes时,视之为“过小”,为了降低额外负担,便采用复杂的memory pool整理方式(后面会介绍),而不再求助于第一级配置器。
接下来,我们来详解这两级空间配置器
第一级配置器剖析
第一级配置器以malloc(),free(),realloc()等C函数执行实际的内存配置、释放、重配置操作,并实现出类似C++ new-handler的机制。所谓的new-handler机制是,你可以要求系统在内存配置无法被满足时,调用一个你所指定的函数。下面我将展示一个示例:
// new_handler example
#include <iostream> // std::cout
#include <cstdlib> // std::exit
#include <new> // std::set_new_handler
void no_memory () {
std::cout << "Failed to allocate memory!\n";
std::exit (1);
}
int main () {
std::set_new_handler(no_memory);
std::cout << "Attempting to allocate 1 GiB...";
char* p = new char [1024*1024*1024];
std::cout << "Ok\n";
delete[] p;
return 0;
}
请注意,STL第一级配置器在调用malloc()和realloc()不成功后,会尝试不断调用“内存不足处理例程”,期望在某次调用之后,获得足够的内存而圆满完成任务,但如果“内存不足例程”未被客户端设定,便会抛出bad_alloc异常信息,或利用exit(1)中止程序。
第二级配置器剖析
STL第二级配置器的做法是,如果区块足够大,超过128bytes时,就移交给第一级配置器处理。当区块小于128bytes时,则以内存池(memory pool)管理,此法又称为次级配置:每次配置一块大内存,并维护对应之自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中取出。如果客端释放小额区块时,就由配置器回收到free-lists中——是的,别忘了,配置器除了负责配置,也负责回收。为了方便管理,STL第二级配置器会主动将任何小额区块的内存需求上调至8的倍数(例如客端要求30bytes,就自动调整为32bytes),并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128bytes的小额区块。
了解了free-list之后我们就会产生一个疑问:如果当分配内存的时候发现free list中没有可用的区块了时,会怎么办呢?
解决方法就是:当发现free list中没有可用区块了时,就会调用一个名为refill()的函数准备为free list重新填充空间。新的空间将调用malloc()取自内存池,缺省取得20个新节点(新区快),但万一内存池空间不足,获得的节点数(区块数)可能小于20。
万一山穷水尽,整个堆空间都不够了,malloc()行动失败,就四处寻找有无“尚未使用区块,且区块足够大”之free lists,找到了就挖出一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc()来配置内存,但它有out-of-memory机制(类似于new-handler机制),或许有机会释放其他的内存拿来此处使用。如果可以就陈宫,否则抛出bad_alloc异常。