书中说到:delete应该是调用对象的析构函数,然后释放内存;,destory也是调用对象的析构函数,vector的析构函数就是先调用了destory,调用对象类型的析构函数,然后调用deallocate释放内存;书上的解释是只是将delete的操作分开了;先是调用析构函数,然后释放内存;记录一下。
STL allocator将new和delete进行了分开操作:
内存分配操作由alloc::allocate() 进行,内存释放操作由alloc::deallocate() 负责;
对象构造操作由::construct() 负责,对象析构操作由::destory() 复杂;
内存的配置与释放:
考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,当配置区块大于128bytes时,则调用第一级配置器,此配置器使用C的malloc和free来完成内存的配置和释放;
这里引用以下别人的关于malloc/free与new/delete的区别与相同之处:
相同:都可用于申请动态内存和释放内存
不同:malloc与free是C++/C 语言的标准库函数,new/delete 是C++的运算符,可以重载。对于非内部数据的对象(c++)而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数, 对象消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free;
用法上,malloc需要指定分配内存的大小,而new却不需要(内部自己操作),并且malloc返回的是void *的指针,需要强制转换,但是new是类型是一样的,且当分配失败时,malloc返回NULL,而new返回bad_alloc 异常,需要捕捉,既没有返回;
new还可以初始化对象,但是malloc只管分配,对象的值是无意义的;
既然new/delete的功能完全覆盖了malloc/free,为什么C++不把malloc/free淘汰出局呢?这是因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
上面说到,当分配区块超过128bytes时,视为足够大,便调用第一级配置器malloc/free,当分配区块小于128bytes时,视之为“过小”,为了降低额外负担,便采用复杂得内存池整理,而不使用第一级配置器;
注:这里的额外负担,意思是说,因为当分配的区块太小,则容易造成内存碎片,这里的额外负担我理解的是说,当你一个程序申请了很多小内存,则释放之后,就可能会产生很多内存碎片,当这个进程在需要大点的内存的时候,就很难获得,这时候可能会进行频繁的内存与磁盘的换进换出,开销会很大,
因此,为了降低额外负担,SGI采用了内存池整理方式,而不在求助于第一级配置器;
第一级配置器没啥好说的,就是对malloc和free的封装;
下面着重说一下第二级配置器:
当区块小于128bytes时,每次通过malloc配置一大块内存,并维护对应之自由链表,为了内存对齐,这里维护了8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128,每次对申请的小内存上调至8的倍数,通过自由链表可以很方便的管理各种尺寸的内存,比如回收小内存,直接将其插入到链表的开头即可,因此是常数时间,这里找合适的节点,其实是通过首次适配来找的,因为这里也不需要进行合并,所以并没有将节点根据地址顺序放入;
由于每一个节点都需要指向下一个节点,为了维护链表,我们需要一个数据结构,用来存储下一个节点,为了不造成额外负担,这里用了一种c++的数据类型
union obj{
union obj* free_list_link;
char client_data[1];
}
这里维护了内存池剩余空间的大小
static char *start_free ;
static char *end_free ;
这里如果没有找到对应大小的节点时,说明链表中没有相对应得空闲块,这样得话,就会调用refill函数,将内存池剩余得空间bytes_left=end_free-start_free;配置到链表中,这里当剩余空间过大的时候,就配置20个需要尺寸的块,将首块分配出去,然后剩下的19块就插入到对应链表的节点处,
如果剩余空间不能分配20个了,那就把剩下的都分配了,如果没剩余空间了,那就调用malloc,分配一块内存给内存池,进行重新分配;如果堆中空间也不足了,那就调用异常来解决
总结一下,这里通过内存池技术,就可以很好的分配小内存,这样就会避免小内存导致的外部碎片,因为它将统一的分配小块内存,这样每个进程,都能很好的利用所分配的内存空间,尽量可以获得大的内存块了。从而空间利用率显著提升,这里也是和非连续的物理地址空间时有关系的。使得每个进程并不是需要一块连续的内存空间