C++项目——内存池
C++项目——内存池
前置知识
std::allocator
c++中所有stl容器都有自己的allocator类用于分配和回收空间,例如vector类中push_back函数的实现方式:
template <class T>
void Vector<T>::push_back(const T& t)
{
// are we out of space?
if (first_free == end)
reallocate(); // gets more space and copies existing elements to it
alloc.construct(first_free, t);
++first_free;
}
其中first_free
指向容器中第一个空闲的块,如果没有空闲的块就调用reallocate()
函数重新分配空间
alloc
是类Allocator<T>
的一个对象,construct方法可以在一个指定区域构建对象,构造完之后让first_free
指向下一个空闲的块
当我们使用new表达式时,在调用拷贝构造函数时会伴随着内存分配,实际上是用了c++的内置操作符new:
void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
new (place_address) type
new (place_address) type (initializer-list)
其中前两行分别用于分配对象内存和分配数组内存,后两行则用于在指定空间构造对象
注意:new表达式 ≠ new运算符,new表达式是不可以被重载的,new表达式底层就是调用这个重载函数
Alloctor类的construct方法底层实现就是一句placement new(定位new)语法
也就是上一个代码块第3行 new (place_address) type
new (first_free) T(const T& t);
告诉编译器“在给定地址 first_free
上构造对象”
placement new 只负责在指定内存上构造对象,内存分配已经由其他逻辑(比如 Allocator 内部)完成了
再来看reallocate函数的实现:
template <class T> void Vector<T>::reallocate() {
// compute size of current array and allocate space for twice as many elements
std::ptrdiff_t size = first_free - elements;
std::ptrdiff_t newcapacity = 2 * max(size, 1);
// allocate space to hold newcapacity number of elements of type T
T* newelements = alloc.allocate(newcapacity);
// construct copies of the existing elements in the new space
uninitialized_copy(elements, first_free, newelements);
// destroy the old elements in reverse order
for (T *p = first_free; p != elements; /* empty */ )
alloc.destroy(--p);
// deallocate cannot be called on a 0 pointer
if (elements)
// return the memory that held the elements
alloc.deallocate(elements, end - elements);
// make our data structure point to the new elements
elements = newelements;
first_free = elements + size;
end = elements + newcapacity;
}
其中Alloctor<T>
类的allocate
成员函数的作用是向系统申请指定个数的长度为sizeof(T)
的连续空间
其底层实现是调用的重载new运算符,分配数组内存:
return operator new[](newcapacity * sizeof(T));
uninitialized_copy
函数是memory头文件的函数,其声明形式如下:
template <class InputIterator, class ForwardIterator>
ForwardIterator
uninitialized_copy ( InputIterator first, InputIterator last,
ForwardIterator result );
elements
指针指向的是vector内部维护的线性表的首地址,该函数的调用实际上将elements
与first_free
所限定的区域里的对象拷贝到由newelements
所指向的新分配的的空间中去,其底层也是使用的是拷贝构造函数。
然后就是Alloctor<T>
类的destory
函数,它有一个参数,指向需要销毁的对象的指针,该函数只进行对象的销毁,不进行内存的回收。实际上这里就是简单的调用析构函数而已
最后是Alloctor<T>
类的deallocate
函数,它有两个参数,第一个指向线性表的首地址,第二个参数指明要内存回收的对象的个数,注意,这里只进行内存回收,而不会进行对象的销毁,其底层使用的是delete重载函数,同new重载函数一样,它也不是我们所熟知的delete表达式,而delete表达式其底层则是调用了delete重载函数来释放内存的,先来看delete有哪些重载函数:
void *operator delete(void*); // free an object
void *operator delete[](void*); // free an array
与new类似,两个重载函数分别对应释放对象和释放数组
内存碎片问题
造成堆内存利用率很低的一个主要原因就是内存碎片化。内存碎片化就是计算机程序在运行过程中,频繁地内存分配与释放引起的内存空间不连续性问题,可能导致内存利用率降低甚至无法分配所需的内存。内存碎片主要分为内碎片和外碎片两种类型。
内碎片
- 定义:内碎片指已分配的内存块未被实际使用的部分。即程序请求的内存小于分配的内存块大小时,多
录知识
余的部分形成内碎片。 - 产生原因:内存分配器通常按固定的对齐规则分配内存块(如对齐到4字节或8字节),分配大小往往
是申请大小的倍数。 - 举例:程序需要13字节内存,但内存分配器按16字节对齐规则分配了16字节。多出的3字节就是内碎
片
外碎片
-
定义:外碎片是指系统中有足够总量的空闲内存,但这些空闲内存不连续,无法满足一个较大的分配
请求。 -
产生原因:频繁的小内存块的分配和释放导致内存分布变得零散和不连续的小块空闲内存无法自动组
合成足够大的连续块。 -
举例:系统中有多个小块空闲内存,总量为100MB,但是由于这些空闲内存块彼此不连续,无法分配
一个需要50MB的大块。
注:内存池的固定大小块分配等机制,可以减少有效外碎片,内存池的内存分配策略根据实际需求制定的
越精细产生的内碎片越少。ps:内存碎片是不可避免只能减少的