先说一个实际的观念:所有的分配动作,一层一层的下去,C++向下最后都会到c这个level(C runtime memory)所提供的malloc函数,这个函数在根据它是在windows或linux等不同操作系统里面的system api去拿到真正的内存。
我们平时用来创建新对象的new关键字其实是 new operator;
new operator主要做两件事:1.分配内存; 2.调用类的构造函数
而分配内存的工作,其实就是调用了C++中自带的 operator new函数
下面是vc98中提供的operator new的源码截图:
从上面的源码中可以看到,operator new其实就是调用malloc函数来进行内存分配的,下面展示一个malloc分配内存后返回的内存空间模型:
浅蓝色部分为我们所指定的size大小,红色的部分是cookie(记录整块的大小),从图中就可以看得出malloc在分配内存时是有很多额外开销overhead的。而我们需要的这块内存size越小,overhead所占整体的比重就会越大。
至于这些额外内存的用途会在后面更新一个《C++内存管理》专栏来讲解。
这时就有一个小问题,STL中的容器背后的allocators是怎么分配内存的呢?就是单纯的使用malloc嘛?还是有一些独特的trick呢?
想要解决上面的疑问,只需要找到allocators的源代码然后搞懂它就可以了。
我们以vector为例,找到vector的源代码,可以看到vector模板类的声明中默认是有allocator的。而STL中所有的容器都是如此,他们的模板类中都默认会有allocator这个类。
我们再找到这个allocator类的定义源码:
可以发现分配器最重要的两个函数: deallocate 和 allocate
我们发现allocate中调用了一个叫做 _Allocate的函数,所以再向下追,看一下_Allocate函数做了什么:
这时可以看到,_Allocate函数中就是调用的operator new函数,而前面已经知道了operator new是通过条用malloc来进行内存的分配。同理也能找到 deallocate中调用了_Deallocate函数,而_Deallocate函数中又调用operator delete最终调用free来继续内存的释放。
看到这我们可以自己使用分配器来进行一下内存的分配和释放,代码如下:
int* p= allocator().allocate(512);
allocator().deallocate(p,512);
这时就可以看到一个重要的问题,释放内存竟然要输入内存的大小!!!要知道我们程序员在打代码时不可能把自己申请的每一个内存的大小都记住,所以这是很不方便的,而对于容器来说却是可行的。
总结:
VC、GCC、BC的allocator只是以::operator new和::operator delete完成allocate()和deallocate(),没有任何特殊设计。
注意:
G++
真正使用的分配器叫做alloc,它是有一定优化策略的。
而前面我们知道,任何的分配内存操作最终都是调用malloc,而malloc就会有overhead,并且分配的内存越小overhead就越严重。所以策略很简单,就是想办法降低malloc的次数。
具体操作如下:
alloc分配器设计了16条链表,每条链表负责 n*8个字节大小。
#1:8字节 #2:12字节 ....... #15:16*8=128字节
所有的容器需要内存时都会向这个分配器要大小,大小首先被调整为8的倍数(比如50被调整为56),然后找对应的链表头,如果链表头下没有内存块,就会调用malloc向操作系统申请一大块内存,然后用对内存做切割,切割出来的用链表串起来。整个的一大块有一组cookie,但切割的每一小块是没有的。
疑问:
实际情况中,目前的版本使用的却是一开始介绍的没有特殊处理的allocator,很奇怪!!!