关于vector容器的deallocator的思考

从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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值