STL空间配置器

一、开场白:

给我的感觉就是,了解是空间配置器的功能,是那么的明了;在看原理,我还是很开心;接下来是360度大转变:

那么长的变量或者函数命名、那么多的宏、不爽,不过,遇上我这种二货,是精华,我也给嚼碎了,下面开始吧:

二、STL是什么:

1.STL(Standard TemplateLibrary),即标准模板库,是一个具有工业强度的,高效的C++程序库。

2.它被容纳于C++标准程序库(C++ StandardLibrary)中,是ANSI/ISO C++标准中最新的也是极具革命性的一部分。

3.该库包含了诸多在计算机科学领域里所常用的基本数据结构和基本算法。为广大C++程序员们提供了一个可扩展的应用框架,高度体现了软件的可复用性。

4.STL(Standard TemplateLibrary,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其他一些组件的集合。

一句话:就是为了有品味的偷懒,设计出来造福广大码农的。

三、STL空间配置器:

1>>为什么要用空间配置器:

在软件开发,使用很多的小块内存,在程序中动态申请,释放。

那么问题就来了:

1:内存碎片问题。(这里是外碎片问题)

2:频繁的小块内存申请,调用malloc,系统调用产生性能问题。

另外说明:

1.内碎片:因为内存对齐、访问效率而产生 。如 用户需要6字节,实际得到8或者10字节的问题,其中的碎片是浪费掉的。

2.外碎片:系统中内存总量足够,但是不连续,所以无法分配给用户使用而产生的浪费。如下图所示:

频繁分配和 释放过后,空白区域不在连续,当程序需要再次分配没存时,虽然零碎的内存加起来远远大过程序需要申请的内存,但是,内存申请,只认连续的,那么不连续,大多就荒废了!

2>>怎么用:

知道了问题,那么空间配置器就是来解决问题的,那么如何解决呢:下面来看看:

在stl_alloc.h中定义了两级配置器,主要思想是申请大块内存池,小块内存直接从内存池中申请,当不够用时再申请新的内存池,还有就是大块内存直接申请。当申请空间大于128字节时调用第一级配置器,第一级配置器没有用operator::new和operator::delete来申请空间,而是直接调用malloc/free和realloc,并且实现了类似c++中new-handler的机制。所谓c++
new handler机制是,你可以要求系统在内存配置需求无法被满足时,调用一个指定的函数。换句话说,一旦::operator::new无法完成任务,在丢出std::bad_alloc异常状态之前,会先调用由客端指定的处理例程,该处理例程通常称为new-handler.new-handler解决内存做法有特定的模式
。SGI第一级配置器的allocate()和realloc都是在调用malloc和realloc不成功后,改调用oom_malloc()和oom_realloc(),后两者都有内循环,不断调用"内存不足处理例程",期望在某次调用之后,获得足够的内存而圆满完成任务。但如果“内存不足处理例程“并未被客端设定,oom_malloc()和oom_realloc便调用_THROW_BAD_ALLOC,
丢出bad_alloc异常信息,或利用exit(1)硬生生中止程序。

     在stl_alloc.h中定义的第二级配置器中,如果区块够大,超过128字节时,就移交给第一级配置器处理。当区块小于128字节时,则以内存池管理,此法又称为次层配置,每次配置一大块内存,并维护对应的自由链表(free-list)。下次若再有相同大小的内存需求,就直接从free-list中拔出,关于自由链表:

如果客端释还小额区块,就由配置器回收到free-lists中,另外,配置器除了负责配置,也负责回收。为了管理方便,SGI第二级配置器会主动将任何小额区块的内存需求量上调至8的倍数。并维护16个free-lists,各自管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,
112,120,128 字节的小额区块。当申请小于等于128字节时就会检查对应的free list,如果free-list中有可用的区块,就直接拿来,如果没有,就准备为对应的free-list 重新填充空间。新的空间将取自内存池,缺省取得20个新节点,如果内存池不足(但是还足以配置一个以上的节点),就返回的相应的节点数.如果当内存池中连一个节点大小都不够时,就申请新的内存池,大小为2*total_bytes+ROUND_UP(heap_size>>4),totoal_bytes 为申请的空间大小,ROUND_UP调整为8的倍数,heap_size为当前总申请内存池的大小。如果申请该内存池成功就把原来内存池中剩下的空间分配给适当的free-list.万一山穷水尽,整个system
heap空间都不够了(以至无法为内存池注入源头活水),malloc()行动失败,就会四处寻找有无"尚有未用区块,且区块足够大 "之free lists.找到了就挖一块交出,找不到就调用第一级配置器。第一级配置器其实也是使用malloc来配置内存。但它有out-of-memory处理机制(类似new-handler机制),或许有机会释放其他的内存拿来此处使用。如果可以就成功,否则发出bad_alloc异常。

实现时,allocator需要维护一个存储16个空闲块列表表头的数组free_list,数组元素i是一个指向块大小为8*(i+1)字节的空闲块列表的表头,一个指向内存池起始地址的指针start_free和一个指向结束地址的指针end_free。空闲块列表节点的结构如下:

union obj
{
union obj * free_list_link;
char client_data[1];
};

这个结构可以看做是从一个内存块中抠出4个字节大小来,当这个内存块空闲时,它存储了下个空闲块,当这个内存块交付给用户时,它存储的时用户的数据。因此,allocator中的空闲块链表可以表示成:

    obj* free_list[16];

下面看看一个博友的伪代码,写的相当不错,值得借鉴:

// 算法:allocate
// 输入:申请内存的大小size
// 输出:若分配成功,则返回一个内存的地址,否则返回NULL
{
if(size 大于 128)
启动第一级分配器直接调用malloc分配所需的内存并返回内存地址;
else
{
将size向上round up成8的倍数并根据大小从free_list中取对应的表头free_list_head
if(free_list_head 不为空)
{
从该列表中取下第一个空闲块并调整free_list,返回free_list_head
}
else
{
调用refill算法建立空闲块列表并返回所需的内存地址
}
}
} // 算法:refill
// 输入:内存块的大小size
// 输出:建立空闲块链表并返回第一个可用的内存地址
{
调用chunk_alloc算法分配若干个大小为size的连续内存区域并返回起始地址chunk和成功分配的块数nobj
if(块数为1)
直接返回 chunk;
else
{
开始在chunk地址块中建立free_list
根据size取free_list中对应的表头元素free_list_head
将free_list_head 指向chunk中偏移起始地址为size的地址处,即free_list_head = (obj*)(chunk+size)
再将整个chunk中剩下的nobj-1个内存块串联起来构成一个空闲列表
返回chunk,即chunk中第一个空闲的内存块
}
} // 算法:chunk_alloc
// 输入:内存块的大小size,预分配的内存块数nobj(以引用传递)
// 输出:一块连续的内存区域的地址和该区域内可以容纳的内存块的块数
{
计算总共所需的内存大小total_bytes
if(内存池足以分配,即end_free-start_free >= total_bytes)
{
则更新start_free
返回旧的start_free
}
else if(内存池不够分配nobj个内存块,但至少可以分配一个)
{
计算可以分配的内存块数并修改nobj
更新start_free并返回原来的start_free
}
else // 内存池连一个内存块都分配不了
{
先将内存池的内存块链入到对应的free_list中后
调用malloc操作重新分配内存池,大小为2倍的total_bytes为附加量,start_free指向返回的内存地址
if(分配不成功)
{
if(16个空闲列表中尚有空闲块)
尝试将16个空闲列表中空闲块回收到内存池中再调用chunk_alloc(size,nobj)
else
调用第一级分配器尝试out of memory机制是否还有用
}
更新end_free为start_free+total_bytes,heap_size为2倍的total_bytes
调用chunk_alloc(size,nobj)
}
} // 算法:deallocate
// 输入:需要释放的内存块地址p和大小size
{
if(size 大于128字节)
直接调用free(p)释放
else
{
将size向上取8的倍数,并据此获取对应的空闲列表表头指针free_list_head
调整free_list_head将p链入空闲列表块中
}
}

以下为一级和二级空间配置器源码:

allocator.h

#pragma once

#include <stdio.h>
#include <stdarg.h> #define __DEBUG__ static string GetFileName(const string& path)
{
char ch = '/'; #ifdef _WIN32
ch = '\\';
#endif size_t pos = path.rfind(ch);
if (pos == string::npos)
{
return path;
}
else
{
return path.substr(pos + 1);
}
}
// 用于调试追溯的trace log
inline static void __trace_debug(const char* function,
const char* filename, int line, char* format, ...)
{
#ifdef __DEBUG__
// 输出调用函数的信息
fprintf(stdout, "【%s:%d】-%s", GetFileName(filename).c_str(), line, function); // 输出用户打的trace信息
va_list args;
va_start (args, format);
vfprintf (stdout, format, args);
va_end (args);
#endif
} #define __TRACE_DEBUG(...) \
__trace_debug(__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__); ////////////////////////////////////////////////////////////////////////////
// 以下是模拟实现SGI STL30版的内存配置器。 // SimpleAlloc统一封装的内存分配的接口
template<class T, class Alloc>
class SimpleAlloc
{
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));
}
}; ///////////////////////////////////////////////////////////////////////////
// 一级空间配置器(malloc/realloc/free)
// // 内存分配失败以后处理的句柄handler类型
typedef void(*ALLOC_OOM_FUN)();
template <int inst>
class __MallocAllocTemplate
{
private:
//static void (* __sMallocAllocOomHandler)();
static ALLOC_OOM_FUN __sMallocAllocOomHandler; static void * OomMalloc(size_t n)
{
ALLOC_OOM_FUN handler;
void* result; //
// 1:分配内存成功,则直接返回
// 2:若分配失败,则检查是否设置处理的handler,
// 有则调用以后再分配。不断重复这个过程,直到分配成功为止。
// 没有设置处理的handler,则直接结束程序。
//
for (;;) {
handler = __sMallocAllocOomHandler;
if (0 == handler)
{
cerr<<"out of memory"<<endl;
exit(-1);
} handler(); result = malloc(n);
if (result)
return(result);
}
} static void *OomRealloc(void* p, size_t n)
{
// 同上
ALLOC_OOM_FUN handler;
void* result; for (;;) {
handler = __sMallocAllocOomHandler;
if (0 == handler)
{
cerr<<"out of memory"<<endl;
exit(-1);
} (*handler)();
result = realloc(p, n);
if (result) return(result);
}
}
public:
static void * Allocate(size_t n)
{
__TRACE_DEBUG("(n:%u)\n", n); void *result = malloc(n);
if (0 == result) result = OomMalloc(n);
return result;
} static void Deallocate(void *p, size_t /* n */)
{
__TRACE_DEBUG("(p:%p)\n", p); free(p);
} static void* Reallocate(void *p, size_t /* old_sz */, size_t new_sz)
{
void * result = realloc(p, new_sz);
if (0 == result) result = OomRealloc(p, new_sz);
return result;
} static void (* SetMallocHandler(void (*f)()))()
{
void (* old)() = __sMallocAllocOomHandler;
__sMallocAllocOomHandler = f;
return(old);
}
}; // 分配内存失败处理函数的句柄函数指针
template <int inst>
ALLOC_OOM_FUN __MallocAllocTemplate<inst>::__sMallocAllocOomHandler = 0; typedef __MallocAllocTemplate<0> MallocAlloc; //#define __USE_MALLOC # ifdef __USE_MALLOC
typedef __MallocAllocTemplate<0> MallocAlloc;
typedef MallocAlloc Alloc;
# else ////////////////////////////////////////////////////////////////////////
// 二级空间配置器 template <bool threads, int inst>
class __DefaultAllocTemplate
{
public:
enum {__ALIGN = 8}; // 排列基准值(也是排列间隔)
enum {__MAX_BYTES = 128}; // 最大值
enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; // 排列链大小 static size_t ROUND_UP(size_t bytes)
{
// 对齐
return ((bytes + __ALIGN - 1) & ~(__ALIGN - 1));
} static size_t FREELIST_INDEX(size_t bytes)
{
// bytes == 9
// bytes == 8
// bytes == 7
return ((bytes + __ALIGN - 1)/__ALIGN - 1);
} union Obj
{
union Obj* _freeListLink; // 指向下一个内存块的指针
char _clientData[1]; /* The client sees this.*/
}; static Obj* volatile _freeList[__NFREELISTS]; // 自由链表
static char* _startFree; // 内存池水位线开始
static char* _endFree; // 内存池水位线结束
static size_t _heapSize; // 从系统堆分配的总大小 // 获取大块内存插入到自由链表中
static void* Refill(size_t n);
// 从内存池中分配大块内存
static char* ChunkAlloc(size_t size, int &nobjs); static void * Allocate(size_t n);
static void Deallocate(void *p, size_t n);
static void* Reallocate(void *p, size_t old_sz, size_t new_sz);
}; // 初始化全局静态对象
template <bool threads, int inst>
typename __DefaultAllocTemplate<threads, inst>::Obj* volatile __DefaultAllocTemplate<threads,inst>::_freeList[__DefaultAllocTemplate<threads, inst>::__NFREELISTS]; template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_startFree = 0;
template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::_endFree = 0;
template <bool threads, int inst>
size_t __DefaultAllocTemplate<threads, inst>::_heapSize = 0;; template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Refill(size_t n)
{
__TRACE_DEBUG("(n:%u)\n", n); //
// 分配20个n bytes的内存
// 如果不够则能分配多少分配多少
//
int nobjs = 20;
char* chunk = ChunkAlloc(n, nobjs); // 如果只分配到一块,则直接这块内存。
if(nobjs == 1)
return chunk; Obj* result, *cur;
size_t index = FREELIST_INDEX(n);
result = (Obj*)chunk; // 把剩余的块链接到自由链表上面
cur = (Obj*)(chunk+n);
_freeList[index] = cur;
for(int i = 2; i < nobjs; ++i)
{
cur->_freeListLink = (Obj*)(chunk+n*i);
cur = cur->_freeListLink;
} cur->_freeListLink = NULL;
return result;
} template <bool threads, int inst>
char* __DefaultAllocTemplate<threads, inst>::ChunkAlloc(size_t size, int &nobjs)
{
__TRACE_DEBUG("(size: %u, nobjs: %d)\n", size, nobjs); char* result;
size_t bytesNeed = size*nobjs;
size_t bytesLeft = _endFree - _startFree; //
// 1.内存池中的内存足够,bytesLeft>=bytesNeed,则直接从内存池中取。
// 2.内存池中的内存不足,但是够一个bytesLeft >= size,则直接取能够取出来。
// 3.内存池中的内存不足,则从系统堆分配大块内存到内存池中。
//
if (bytesLeft >= bytesNeed)
{
__TRACE_DEBUG("内存池中内存足够分配%d个对象\n", nobjs); result = _startFree;
_startFree += bytesNeed;
}
else if (bytesLeft >= size)
{
__TRACE_DEBUG("内存池中内存不够分配%d个对象,只能分配%d个对象\n", nobjs, bytesLeft / size);
result = _startFree;
nobjs = bytesLeft / size;
_startFree += nobjs*size;
}
else
{
// 若内存池中还有小块剩余内存,则将它头插到合适的自由链表
if (bytesLeft > 0)
{
size_t index = FREELIST_INDEX(bytesLeft);
((Obj*)_startFree)->_freeListLink = _freeList[index];
_freeList[index] = (Obj*)_startFree;
_startFree = NULL; __TRACE_DEBUG("将内存池中剩余的空间,分配给freeList[%d]\n", index);
} // 从系统堆分配两倍+已分配的heapSize/8的内存到内存池中
size_t bytesToGet = 2*bytesNeed + ROUND_UP(_heapSize>>4);
_startFree = (char*)malloc(bytesToGet);
__TRACE_DEBUG("内存池空间不足,系统堆分配%u bytes内存\n", bytesToGet); //
// 【无奈之举】
// 如果在系统堆中内存分配失败,则尝试到自由链表中更大的节点中分配
//
if (_startFree == NULL)
{
__TRACE_DEBUG("系统堆已无足够,无奈之下,智能到自由链表中看看\n"); for(int i = size; i <= __MAX_BYTES; i+=__ALIGN)
{
Obj* head = _freeList[FREELIST_INDEX(size)];
if (head)
{
_startFree = (char*)head;
_freeList[FREELIST_INDEX(size)] = head->_freeListLink;
_endFree = _startFree+i;
return ChunkAlloc(size, nobjs);
}
} //
// 【最后一根稻草】
// 自由链表中也没有分配到内存,则再到一级配置器中分配内存,
// 一级配置器中可能有设置的处理内存,或许能分配到内存。
//
__TRACE_DEBUG("系统堆和自由链表都已无内存,一级配置器做最后一根稻草\n");
_startFree = (char*)MallocAlloc::Allocate(bytesToGet);
} // 从系统堆分配的总字节数。(可用于下次分配时进行调节)
_heapSize += bytesToGet;
_endFree = _startFree + bytesToGet; // 递归调用获取内存
return ChunkAlloc(size, nobjs);
} return result;
} template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Allocate(size_t n)
{
__TRACE_DEBUG("(n: %u)\n", n); //
// 若 n > __MAX_BYTES则直接在一级配置器中获取
// 否则在二级配置器中获取
//
if (n > __MAX_BYTES)
{
return MallocAlloc::Allocate(n);
} size_t index = FREELIST_INDEX(n);
void* ret = NULL; //
// 1.如果自由链表中没有内存则通过Refill进行填充
// 2.如果自由链表中有则直接返回一个节点块内存
// ps:多线程环境需要考虑加锁
//
Obj* head = _freeList[index];
if (head == NULL)
{
return Refill(ROUND_UP(n));
}
else
{
__TRACE_DEBUG("自由链表取内存:_freeList[%d]\n", index); _freeList[index] = head->_freeListLink;
return head;
}
} template <bool threads, int inst>
void __DefaultAllocTemplate<threads, inst>::Deallocate(void *p, size_t n)
{
__TRACE_DEBUG("(p:%p, n: %u)\n",p, n); //
// 若 n > __MAX_BYTES则直接归还给一级配置器
// 否则在放回二级配置器的自由链表
//
if (n > __MAX_BYTES)
{
MallocAlloc::Deallocate(p, n);
}
else
{
// ps:多线程环境需要考虑加锁
size_t index = FREELIST_INDEX(n); // 头插回自由链表
Obj* tmp = (Obj*)p;
tmp->_freeListLink = _freeList[index];
_freeList[index] = tmp;
}
} template <bool threads, int inst>
void* __DefaultAllocTemplate<threads, inst>::Reallocate(void *p, size_t old_sz, size_t new_sz)
{
void * result;
size_t copy_sz; if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) {
return(realloc(p, new_sz));
}
if (ROUND_UP(old_sz) == ROUND_UP(new_sz))
return p; result = Allocate(new_sz);
copy_sz = new_sz > old_sz? old_sz : new_sz;
memcpy(result, p, copy_sz);
Deallocate(p, old_sz);
return result;
} typedef __DefaultAllocTemplate<false, 0> Alloc;
#endif // __USE_MALLOC // 通过__TRACE_DEBUG做白盒测试 // 测试内存池的一级、二级配置器功能
void Test1()
{
// 测试调用一级配置器分配内存
cout<<"测试调用一级配置器分配内存"<<endl;
char*p1 = SimpleAlloc<char, Alloc>::Allocate(129);
SimpleAlloc<char, Alloc>::Deallocate(p1, 129); // 测试调用二级配置器分配内存
cout<<"测试调用二级配置器分配内存"<<endl;
char*p2 = SimpleAlloc<char, Alloc>::Allocate(128);
char*p3 = SimpleAlloc<char, Alloc>::Allocate(128);
char*p4 = SimpleAlloc<char, Alloc>::Allocate(128);
char*p5 = SimpleAlloc<char, Alloc>::Allocate(128);
SimpleAlloc<char, Alloc>::Deallocate(p2, 128);
SimpleAlloc<char, Alloc>::Deallocate(p3, 128);
SimpleAlloc<char, Alloc>::Deallocate(p4, 128);
SimpleAlloc<char, Alloc>::Deallocate(p5, 128); for (int i = 0; i < 21; ++i)
{
printf("测试第%d次分配\n", i+1);
char*p = SimpleAlloc<char, Alloc>::Allocate(128);
}
} // 测试特殊场景
void Test2()
{
cout<<"测试内存池空间不足分配20个"<<endl;
// 8*20->8*2->320
char*p1 = SimpleAlloc<char, Alloc>::Allocate(8); char*p2 = SimpleAlloc<char, Alloc>::Allocate(8); cout<<"测试内存池空间不足,系统堆进行分配"<<endl;
char*p3 = SimpleAlloc<char, Alloc>::Allocate(12);
} // 测试系统堆内存耗尽的场景
void Test3()
{
cout<<"测试系统堆内存耗尽"<<endl; SimpleAlloc<char, Alloc>::Allocate(1024*1024*1024);
//SimpleAlloc<char, Alloc>::Allocate(1024*1024*1024);
SimpleAlloc<char, Alloc>::Allocate(1024*1024); // 不好测试,说明系统管理小块内存的能力还是很强的。
for (int i = 0; i < 100000; ++i)
{
char*p1 = SimpleAlloc<char, Alloc>::Allocate(128);
}
}

四、讨论一个问题:

对于内存池来说,这样的大内存池子是用malloc来的,想释放,当然用free好了,但是,对于从内存池中跑向自由链表之下的小块内存,如何释放?

其实自由链表中的内存既内有还给操作系统,也没有还给内存池,在自由链表中,且配置器的所有方法,成员都是静态的,那么他们就是存放在静态区。这些内存即在程序结束释放。

咬碎STL空间配置器的更多相关文章

  1. stl空间配置器线程安全问题补充

    摘要 在上一篇博客<STL空间配置器那点事>简单介绍了空间配置器的基本实现 两级空间配置器处理,一级相关细节问题,同时简单描述了STL各组件之间的关系以及设计到的设计模式等. 在最后,又关 ...

  2. 【转】STL空间配置器

    STL空间配置器(allocator)在所有容器内部默默工作,负责空间的配置和回收.STL标准为空间配置器定义了标准接口(可见<STL源码剖析>P43).而具体实现细节则由各编译器实现版本 ...

  3. STL空间配置器

    1.什么是空间配置器? 空间配置器负责空间配置与管理.配置器是一个实现了动态空间配置.空间管理.空间释放的class template.以内存池方式实现小块内存管理分配.关于内存池概念可以点击:内存池 ...

  4. STL空间配置器那点事

    STL简介 STL(Standard Template Library,标准模板库),从根本上说,STL是一些“容器”的集合,这些“容器”有list,vector,set,map等,STL也是算法和其 ...

  5. STL空间配置器解析和实现

    STL空间配置器的强大和借鉴作用不言而喻,查阅资料,发现了Dawn_sf已经对其有了极其深入和详细的描述,所以决定偷下懒借用其内容,只提供自己实现STL空间配置器的源码,具体解析内容参考:(一)STL ...

  6. 【陪你系列】5 千字长文+ 30 张图解 | 陪你手撕 STL 空间配置器源码

    大家好,我是小贺. 点赞再看,养成习惯 文章每周持续更新,可以微信搜索「herongwei」第一时间阅读和催更,本文 GitHub https://github.com/rongweihe/MoreT ...

  7. STL——空间配置器(构造和析构基本工具)

    以STL的运用角度而言,空间配置器是最不需要介绍的东西,它总是隐藏在一切组件(更具体地说是指容器,container)的背后,默默工作,默默付出.但若以STL的实现角度而言,第一个需要介绍的就是空间配 ...

  8. STL空间配置器、vector、list、deque、map复习

    本文写于2017-03-03,从老账号迁移到本账号,原文地址:https://www.cnblogs.com/huangweiyang/p/6440830.html STL的六大组件:容器.算法.迭代 ...

  9. STL——空间配置器(SGI-STL)

    一. 空间配置器标准接口 参见<STL源码剖析>第二章-2.1.<memory>文件. 二.具备次配置力的SGI空间配置器 1. SGI STL的配置器与众不同,也与标准规范不 ...

随机推荐

  1. WebForm 生成并显示二维码

    Generate and display QRCode in WebForm. 项目引用 QRCoder生成并显示 QRCode 项目引用 QRCoder How to use QRCoder Via ...

  2. 最新的 iOS 申请证书与发布流程

    申请流程. 1. 申请钥匙串文件 进入  (Launchpad),找到   (我的是在其他里面找到的),运行后再左上角 存储在桌面就好了,然后就完成退出钥匙串工具就可以了. 2.申请开发证书,发布证书 ...

  3. 浏览器全屏之requestFullScreen全屏与F11全屏

    一.简介 浏览器全屏有两种方式,一种是HTML5新增的requestFullscree全屏,另一种是摁F11实现的全屏,本文将详解两种全屏的特点以及实现代码. 二.requestFullscreen全 ...

  4. JS实现全选功能

    000. 开始 学习JS有一段时间了,最近看了一些JS练手的小demo实例,自己也尝试着用JS进行实现. 全选功能是在很多注册页面.获取用户兴趣爱好.让用户勾选一些选项等页面中常见的一种效果,主要有全 ...

  5. [SCOI2008]斜堆

    题目大意 1.题目描述 斜堆(skew heap)是一种常用的数据结构. 它也是二叉树,且满足与二叉堆相同的堆性质: 每个非根结点的值都比它父亲大.因此在整棵斜堆中,根的值最小. . 但斜堆不必是平衡 ...

  6. [ZJOI2014]力

    推公式发现(这不是水题吗,这要推吗) \[E_i=\Sigma^{i-1}_{j=1} \frac{q_j}{(i-j)^2} - \Sigma^{n}_{j=i+1} \frac{q_j}{(i-j ...

  7. mongo分布式集群搭建手记

    一.架构简介 目标 单机搭建mongodb分布式集群(副本集 + 分片集群),演示mongodb分布式集群的安装部署.简单操作. 说明 在同一个vm启动由两个分片组成的分布式集群,每个分片都是一个PS ...

  8. Java中动态代理工作流程

    当谈到动态代理就会想到接口,因为接口是一种规范,动态代理对象通过接口便会很清楚地知道他的实现类(被代理对象)是何种类型的(即有哪些方法).Now,然我们来开始编写一个例子来了解动态代理的全过程: 第一 ...

  9. Spring服务定制

    问题总述 ​ 我们都知道如果使用Spring来进行bean管理的时候.如果同一个接口的实现类存在两个,直接使用@Autowired注解来实现bean注入,会在启动的时候报异常.我们通常的做法是使用@R ...

  10. python全栈开发-Day5 元组、字典

    python全栈开发-Day5  元组.字典 一.前言 首先,不管学习什么数据类型,我们都带着以下几个问题展开学习: #1:基本使用 1 .用途 2 .定义方式 3.常用操作+内置的方法 #2:该类型 ...