从STL源码剖析这本书上得到的vector容器的deallocate函数如下:
void deallocate() { // 释放所有的分配的内存,为重新开辟一大段空间做准备,因为vector要求连续的数组
if (start) {
data_allocator::deallocate(start, end_of_storage - start);
}
}
这段代码看起来很合理,就是释放[start, end_of_storage)这一段内存空间。现在继续看data_allocator::deallocate。
在这里我先将我的疑问提出来:
我们知道,vector在定义时需要申请连续的内存,以支持对元素的随机存取,申请内存的任务由data_allocator完成。data_allocator也就是空间配置器,其中二级空间配置器采用了free_list这样的内存管理方法。回归正题,现在要释放当前vector已经分配的所有内存,这段内存可能是从区块大小为8的free_list[0]中挖取出来的16个区块。现在这整段区块的大小为128字节,回收时应该放在区块单位大小为128的那个free_list[i]中。此时这一段区块将被插入free_list[i]的头部,在插入之间free_list[i]为nullptr还好,要是不为nullptr,如何保证即将插入的内存与free_list[i]原有的内存是连续的呢。
---------------------------------------------------------------------------------------------------------------------
下面是对代码的一些分析
其中data_allocator是一个类型,deallocate是一个静态成员函数。
data_allocator的声明如下:
typedef simple_alloc<value_type, Alloc> data_allocator;
ok,现在又要查看simple_alloc<value_type, Alloc>,有点套娃,该类模板是对Alloc的一个封装
// 对alloc进行封装
template <class T, class Alloc>
class simple_alloc {
public:
static T *allocate(size_t n) {
return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof(T));
}
static T *allocate(void) {
return (T*)Alloc::allocate(sizeof(T));
}
static void deallocate(T *p, size_t n) {
if (0 != n) Alloc::deallocate(p, n * sizeof(T));
}
static void deallocate(T *p) {
Alloc::deallocate(p, sizeof(T));
}
};
到这里就可以发现data_allocator::deallocate(start, end_of_storage - start)调用的其实是空间配置器的deallocate函数。该函数负责将[p, p + n * sizeof(T) 这段内存回收到合适的free_list中.
--------------------------------------------------------------------------------------------------------------------------------
解答:在push_back函数中,在分配内存时,vector永远只会从free_list[i]中拿一个区块,不存在将多个区块分配给同一个vector对象的情况,这一点由下面的代码可以看出来:
const size_type new_size = old_size != 0 ? 2 * old_size : 1;
这说明在vector的容量不足时是直接重新分配一个比原来大两倍的区块:
下面是测试的代码
TinySTL::vector<int> b; // b将从区块大小为8的free_list中获取内存
b.push_back(1);
b.push_back(1);
b.push_back(1);
b.push_back(1);
b.push_back(1);
b.push_back(1);
b.push_back(1);
b.push_back(1);
上面的操作中内存的分配过程为:
- 定位到free_list[0],此时free_list[0]为nullptr,需要初始化free_list[0],free_list[0]将得到20个大小为8的区块,并拿出一个给b用。
- 由于之前分配的区块大小为8,容量充足,不需要重新分配内存。
- 此时需要的总容量为3*4=12 > 8,需要重新分配内存,这个时候直接将需要的容量翻倍,为2*8=16,由于free_list[1]是nullptr,需要初始化,得到20个区块,拿出一个给b用。注意这个时候大小为8的区块需要进行回收
- 一直到第5个push_back操作,容量都是够用的。到第五个push_bak()操作时需要的容量为4*5=20 > 16,又需要重新分配,此时又按照之前的步骤初始化free_list[3]。注意这个时候大小为16的区块需要进行回收
- 此后的[push_back操作都不需要重新分配内存,因为此时容量充足。
最后用用如下的函数打印一下free_list:
void alloc::showAddress() {
std::cout << std::endl;
std::cout << "------------------------Address of free list------------------------" << std::endl;
for (int i = 0; i < alloc::ENFreeLists::NFREELISTS; ++i) {
alloc::obj* begin = alloc::free_list[i];
if (begin == nullptr) {
std::cout << "free_list[" << i << "]: " << "null" << std::endl;
}
else {
std::cout << "free_list[" << i << "]: ";
alloc:obj* cur = begin;
while (cur != nullptr) {
std::cout << cur << "->";
cur = cur->next; // 获得下一个区块的首地址
}
std::cout << "nullptr" << std::endl;
}
}
}
得到:
可以看到区块大小为8和16的free_list有20个区块,而大小为32的free_list只有19个区块,因为被b用掉一个。
那么为什么要分配给free_list[i] 20个区块呢,这是因为可能有如下的操作
vector<int> a(16);
vector<int> b(16);