C++学习笔记(十) 内存机制与Allocator

转载 2016年08月30日 11:21:11

C++为我们提供了安全的内存空间申请方式与释放方式,但是new与delete表达式却是把空间的分配回收与对象的构建销毁紧紧的关联在一起。实际上,作为与C语言兼容的语言,C++也为我们提供了更加底层的内存操作方式的。

谈C++就离不开STL,考虑一下vector<>类的机制,为了高效率的增加与删除元素,它并不会在我们每次进行添加或删除操作时进行内存的分配与回收,而是会提前预留下一片空间。我们通过size函数可以得到容器内元素的个数,通过capacity函数则可以得到该容器的实际大小。实际上每个容器都有自己的Allocator类,用于进行空间的分配与回收,对象的构造与销毁。下面的代码来自与《C++ primer》,是vector类的push_back函数的一种可能实现方式:

[cpp] view plain copy
  1. template <class T>  
  2. void Vector<T>::push_back(const T& t)  
  3. {  
  4. // are we out of space?  
  5.     if (first_free == end)  
  6.         reallocate(); // gets more space and copies existing elements to it  
  7.     alloc.construct(first_free, t);  
  8.     ++first_free;  
  9. }  

first_free指向容器中第一个空闲的块,如果已经没有空闲块了,则通过reallocate函数重新分配。alloc是Alloctor<T>类的一个对象,调用其construct方法可在一个指定的区域构建对象,调用的是类型T的拷贝构造函数。在构造完成之后,让first_free指向下一个空闲块。

当我们使用new表达式,来调用拷贝构造函数时实际上时伴随着空间的分配的。那么Allocator是怎么做到的呢?实际上它是调用了C++的一个内置的操作符:

[cpp] view plain copy
  1. void *operator new(size_t); // allocate an object  
  2. void *operator new[](size_t); // allocate an array  
  3. new (place_address) type  
  4. new (place_address) type (initializer-list)  

这写重载操作符函数可以进行内存的分配以及在指定的内存空间进行对象的构造。需要注意的是它们并非new表达式,new表达式是不可以被重载的,实际上new表达式底层也就是调用了这些重载函数的。前两个用内存的分配,后两个则用于对象的构造。Alloctor<T>类的construct方法底层实现实际上就是一句调用而已:

[cpp] view plain copy
  1. new (first_free) T(const T& t);  

我们再来看一下reallocate函数的实现:

[cpp] view plain copy
  1. template <class T> void Vector<T>::reallocate() {  
  2. // compute size of current array and allocate space for twice as many elements  
  3.     std::ptrdiff_t size = first_free - elements;  
  4.     std::ptrdiff_t newcapacity = 2 * max(size, 1);  
  5. // allocate space to hold newcapacity number of elements of type T  
  6.     T* newelements = alloc.allocate(newcapacity);  
  7. // construct copies of the existing elements in the new space  
  8.     uninitialized_copy(elements, first_free, newelements);  
  9. // destroy the old elements in reverse order  
  10.     for (T *p = first_free; p != elements; /* empty */ )  
  11.         alloc.destroy(--p);  
  12. // deallocate cannot be called on a 0 pointer  
  13.     if (elements)  
  14. // return the memory that held the elements  
  15.         alloc.deallocate(elements, end - elements);  
  16. // make our data structure point to the new elements  
  17.     elements = newelements;  
  18.     first_free = elements + size;  
  19.     end = elements + newcapacity;  
  20. }  

这个实现就稍微复杂一点了,逻辑上我就不说了,着重说明一下其中几个函数的使用吧。首先是Alloctor<T>类的allocate成员函数的使用,它的作用是向系统申请指定个数的长度为sizeof(T)的连续空间,其底层实现是:

[cpp] view plain copy
  1. return operator new[](newcapacity * sizeof(T));  

uninitialized_copy函数实际上是memory头文件中的一个函数,它的声明形式如下:

[cpp] view plain copy
  1. template <class InputIterator, class ForwardIterator>  
  2.          ForwardIterator  
  3.            uninitialized_copy ( InputIterator first, InputIterator last,  
  4.                                 ForwardIterator result );  

elements指针指向的是vector内部维护的线性表的首地址,该函数的调用实际上将elements与first_free所限定的区域里的对象拷贝到由newelements 所指向的新分配的的空间中去,其底层也是使用的是拷贝构造函数。

然后是关于Alloctor<T>类的destroy成员函数的分析,它有一个参数,指向需要销毁的对象的指针,该函数只进行对象的销毁,但不进行内存的回收。实际上这里就是简单的调用析构函数而已(不要质疑,析构函数确实可以通过指针直接调用的哦)。

接下来是关于Alloctor<T>类的deallocate成员函数的分析,它有两个参数,第一个指向线性表的首地址,第二个参数指明要内存回收的对象的个数,注意,这里只进行内存回收,而不会进行对象的销毁,其底层使用的是delete重载函数,同new重载函数一样,它也不是我们所熟知的delete表达式,而delete表达式其底层则是调用了delete重载函数来释放内存的,先来看delete有哪些重载函数:

[cpp] view plain copy
  1. void *operator delete(void*); // free an object  
  2. void *operator delete[](void*); // free an array  

这两个版本是分别用来释放单个对象以及数组对象的。

小结

C++底层为我们提供了内存分配与回收,对象创建与释放的单独的手段,它们是operator new, placement new, operator delete以及析构函数(不存在placement delete)。通过这四种手段,我们可以灵活的进行资源的合理管理,但它们是属于较低层次的手段,STL为我们提供了Allocator类能很好的为我们做这一切,所以,如无必要,我们最好使用Allocator类来进行内存的管理。进行这种级别的内存管理,处理在效率上的提升外,还能有效的减少内存碎片的诞生,这在某些内存资源有限(例如嵌入式设备)的情况下将是一种非常有效的方式。

参考文献:

《C++ Primer》Stanley B.Lippman Barbara E.Moo

相关文章推荐

C++学习笔记七——内存管理机制

博文地址:http://blog.csdn.net/u01340105 Windows内存管理器 工作集(WorkingSet):进程虚拟空间中实际被映射到物理内存页面的那部分被称为工作集; ...

memcache学习——内存管理机制slab allocator

大致浏览了一下memcached的源码,但是并没有对相关的知识点进行总结和记录,所以很快就忘了,这次打算将memcached的源码再学习一遍,并进行总结归纳。 memcached模块化设计比较好...

memcached源码学习-内存管理机制slab allocator

前端时间大致浏览了一下memcached的源码,但是并没有对相关的知识点进行总结和记录,所以很快就忘了,这次打算将memcached的源码再学习一遍,并进行总结归纳。     memcached模块...

zz : memcached源码学习-内存管理机制slab allocator

zz : http://blog.csdn.net/tankles/article/details/7027645 前端时间大致浏览了一下memcached的源码,但是并没有对相关的知识...

C++学习笔记(九) 异常机制

同大多数的高级语言一样,C++也有自己的异常处理机制,用于方便的处理程序运行过程中可以预料但不可避免的错误。 C++的异常抛出方法是用throw关键字,同java不一样,C++可以抛出任何类型,包括...

【菜鸟C++学习笔记】22. 参数的传递机制

1、按值传递 顾名思义,把变量的值直接传递给函数,如下面的例子: #include using namespace std; void swap(int x, int y) { i...
  • wwjra
  • wwjra
  • 2012-10-10 23:23
  • 318

【菜鸟C++学习笔记】23. 对象的传递机制

上次说的三种参数传递方式,对于一个数据来说没太大的差别,要是换做是一个具有n个数据成员的对象,那在内存中就需要建立n个副本,这种开销就大了去了,另外的原因如下: 1、对象的传递 对象作为参数传递一...
  • wwjra
  • wwjra
  • 2012-10-12 22:27
  • 264

C/C++学习笔记8:内存中数据对齐的问题总结

1:什么是数据对齐? 数据对齐是指数据所在的内存地址必须是该数据长度的整数倍。

c++学习笔记—动态内存与智能指针浅析

我们的程序使用内存包含以下几种: 静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量,在使用之前分配,在程序结束时销毁。 栈内存用来保存定义在函数内部的非st...

C++学习笔记(一)——内存分配问题

内存分配问题 VC调试时按Alt+8、Alt+7、Alt+6和Alt+5,打开汇编窗口、堆栈窗口、内存窗口和寄存器窗口。关于各种变量的内存如下。 VC运行结果: VS运行结果: (1)寄...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)