一、前言
在STL中,容器是其中的重中之重,基本的STL中的算法,仿函数等都是围绕着容器实现的功能。而,内存配置器,是容器的实现的基础。所以,我第一次要去编写便是内存配置器的实现。在STL中,内存配置器的实现是在stl_alloc.h中。
二、配置器原理简要介绍
在SGI STL中配置分为两级,第一级配置器和第二级配置器。两者关系如下:
图1:第一级配置器和第二级配置器
在SGI STL中内存的配置器分为两级,第一级配置器和第二级配置器。第一级配置器就是,直接调用系统的malloc分配内存。对于小于128byte的内存分配请求,我们使用第二级内存配置器。第二级内存配置器是一个内存池,其中共有16个已分配好的区块形成的链表。这16个链表的中区块的大小依次是8,16,24....128byte,都8的倍数。每次请求小于等于128byte时,把请求的大小上调到最接近的8的倍数,比如,7就上调为8,30就上调为32,然后找到对应大小区块的链表,从中取下一个区块返回给请求。
第二级配置器使用内存池的好处就是,可以避免太多小额区块造成的内存破碎。同时,每次分配内存都需要调用malloc去分配,malloc调用的消耗的时间等资源是一定的,对于大区块的分配这样的消耗的时间等资源,是没有什么的。但是对于小额区块的,它的消耗就显得太不值的了。我们可以采用一次预分配一块连续的大区块,把它串成一个定额大小的区块链表,(8的倍数字节),下次使用的时候,从对应预分配的区块链表中找一个能够满足大小的,最接近的区块直接返回给请求,这样就可以避免对于小区块的malloc调用。同时对于小区块的释放,可以直接把它加入到内存池中对应大小的链表中即可。
图2:把连续大区块串成小额区块
在第二级配置器中维持一个free_list[16]数组,其中存储着8-128byte各种大小区块的链表的头节点地址。
图3:free_list[16]结构
每次分配只要从适当大小的链表中取出第一个节点,返回给请求,让free_list对应的位置的保存链表的下一个节点地址。释放的时候,对于小于等于128byte的区块,只要把它插入对应大小区块链表的头,然后调整保存的链表头结点的地址就可以了。当需求大小区块的使用完了的时候,可以利用malloc一次分配适当大小的大区块,然后把它分割为对应大小的小区块,在把它们串联起来形成链表,把第一个节点地址存入对应的free_list的位置中。
三、实现源代码
上面只是配置器的简介,在源码中有详细的注释。源码如下:
1 /************************************************************************* 2 > File Name: stl_alloc_wjzh.h 3 > Author: wjzh 4 > Mail: [email protected] 5 > Created Time: 2014年10月31日 星期五 16时06分23秒 6 ************************************************************************/ 7 8 #ifndef __SGI_STL_INTERNAL_ALLOC_WJZH_H 9 #define __SGI_STL_INTERNAL_ALLOC_WJZH_H 10 11 #ifdef __SUNPRO_CC 12 # define __PRIVATE public 13 #else 14 # define __PRIVATE private 15 #endif 16 17 #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG 18 # define __USE_MALLOC 19 #endif 20 21 #if 0 22 # include <new> 23 # define __THROW_BAD_ALLOC throw bad_alloc 24 #elif !defined(__THROW_BAD_ALLOC) 25 //# include <iostream.h> 26 #include <iostream> 27 # define __THROW_BAD_ALLOC std::cerr << "out of memory" << std::endl; exit(1) 28 #endif 29 30 #ifndef __ALLOC 31 # define __ALLOC alloc 32 #endif 33 #ifdef __STL_WIN32THREADS 34 # include <windows.h> 35 #endif 36 37 #include <stddef.h> 38 #include <stdlib.h> 39 #include <string.h> 40 #include <assert.h> 41 #ifndef __RESTRICT 42 # define __RESTRICT 43 #endif 44 45 #if !defined(__STL_PTHREADS) && !defined(_NOTHREADS) 46 && !defined(__STL_SGI_THREADS) && !defined(__STL_WIN32THREADS) 47 # define _NOTHREADS 48 #endif 49 50 #ifdef __STL_PTHREADS 51 # include <pthread.h> 52 # define __NODE_ALLOCATOR_LOCK 53 if (threads) pthread_mutex_lock(&__node_allocator_lock) 54 # define __NODE_ALLOCATOR_UNLOCK 55 if (threads) pthread_mutex_unlock(&__node_allocator_lock) 56 # define __NODE_ALLOCATOR_THREADS true 57 # define __VOLATILE volatile 58 # endif 59 # ifdef __STL_WIN32THREADS 60 # define __NODE_ALLOCATOR_LOCK 61 EnterCriticalSection(&__node_allocator_lock) 62 # define __NODE_ALLOCATOR_UNLOCK 63 LeaveCriticalSection(&__node_allocator_lock) 64 # define __NODE_ALLOCATOR_THREADS true 65 # define __VOLATILE volatile 66 # endif 67 68 # ifdef __STL_SGI_THREADS 69 extern "C" { 70 extern int __us_rsthread_malloc; 71 } 72 # define __NODE_ALLOCATOR_LOCK if (threads && __us_rsthread_malloc) 73 { __lock(&__node_allocator_lock); } 74 # define __NODE_ALLOCATOR_UNLOCK if(threads && __us_rsthread_malloc) 75 { __unlcok(&__node_allocator_lock); } 76 # define __NODE_ALLOCATOR_THREADS true 77 # define __VOLATILE volatile 78 # endif 79 80 # ifdef _NOTHREADS 81 # define __NODE_ALLOCATOR_LOCK 82 # define __NODE_ALLOCATOR_UNLOCK 83 # define __NODE_ALLOCATOR_THREADS false 84 # define __VOLATILE 85 # endif 86 87 __STL_BEGIN_NAMESPACE 88 89 #if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) 90 #pragma set woff 1174 91 #endif 92 93 #ifdef __STL_STATIC_TEMPLATE_MEMBER_BUG 94 # ifdef __DECLARE_GLOBALS_HERE 95 void (* __malloc_alloc_oom_handler)() = 0; 96 #else 97 extern void (* __malloc_alloc_oom_handler)(); 98 # endif 99 #endif 100 101 template <int inst> 102 class __malloc_alloc_template { 103 private: 104 static void *oom_malloc(size_t); 105 static void *oom_realloc(void *, size_t); 106 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG 107 static void (* __malloc_alloc_oom_handler)(); 108 #endif 109 public: 110 static void* allocate(size_t n) 111 { 112 void *result = malloc(n); 113 if (0 == result) result = oom_malloc(n); 114 return result; 115 } 116 117 static void deallocate(void *p, size_t) 118 { 119 free(p); 120 } 121 122 static void * reallocate(void *p, size_t , size_t new_sz) 123 { 124 void *result = realloc(p, new_sz); 125 if (0 == result) result = oom_realloc(p, new_sz); 126 return result; 127 } 128 129 static void (* set_malloc_handler(void (*f)()))() 130 { 131 void (* old)() = __malloc_alloc_oom_handler; 132 __malloc_alloc_oom_handler = f; 133 return(old); 134 } 135 136 }; 137 138 // malloc_alloc out-of-memory handling 139 // 分配内存时,没有内存时的处理 140 141 #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG 142 template <int inst> 143 void (* __malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0; 144 #endif 145 146 template <int inst> 147 void * __malloc_alloc_template<inst>::oom_malloc(size_t n) 148 { 149 void (* my_malloc_handler)(); 150 void *result; 151 for (;;) 152 { 153 my_malloc_handler = __malloc_alloc_oom_handler; 154 if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; } 155 (*my_malloc_handler)(); 156 result = malloc(n); 157 if (result) return (result); 158 } 159 } 160 161 template <int inst> 162 void * __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n) 163 { 164 void (* my_malloc_handler)(); 165 void *result; 166 167 for (;;) 168 { 169 my_malloc_handler = __malloc_alloc_oom_handler; 170 if (0 == my_malloc_handler) { __THROW_BAD_ALLOC; } 171 (*my_malloc_handler)(); 172 result = realloc(p, n); 173 if (result) return (result); 174 } 175 } 176 177 typedef __malloc_alloc_template<0> malloc_alloc; 178 179 template<class T, class Alloc> 180 class simple_alloc 181 { 182 public: 183 static T *allocate(size_t n) 184 { 185 return 0 == n ? 0 : (T*) Alloc::allocate(n * sizeof(T)); 186 } 187 static T *allocate(void) 188 { 189 return (T*) Alloc::allocate(sizeof(T)); 190 } 191 static void deallocate(T *p, size_t n) 192 { 193 if (0 != n) Alloc::deallocate(p, n * sizeof(T)); 194 } 195 static void deallocate(T *p) 196 { 197 Alloc::deallocate(p, sizeof(T)); 198 } 199 }; 200 201 // Allocator adaptor to check size arguments for debugging. 202 template <class Alloc> 203 class debug_alloc 204 { 205 private: 206 enum { extra = 8}; // Size of space used to store size. Note 207 // that this must be large enough to preserve 208 // alignment. 209 210 public: 211 static void * allocate(size_t n) 212 { 213 char *result = (char *)Alloc::allocate(n + extra); 214 *(size_t *)result = n; //前size_t大小用来记录result的大小,实际预分配了extra个字节,用来存储大小, 215 //但是只用size_t字节,因为不同系统size_t大小不同,8个字节足够满足所有系统了 216 return result + extra; 217 } 218 219 static void deallocate(void *p, size_t n) 220 { 221 char * real_p = (char *)p - extra; 222 assert(*(size_t *)real_p == n); 223 Alloc::deallocate(real_p, n + extra); 224 } 225 226 static void * reallocate(void *p, size_t old_sz, size_t new_sz) 227 { 228 char * real_p = (char *)p - extra; 229 assert(*(size_t *)real_p == old_sz); 230 char * result = (char *) 231 Alloc::reallocate(real_p, old_sz + extra, new_sz+ extra); 232 *(size_t *)result = new_sz; 233 return result + extra; 234 } 235 236 }; 237 238 #ifdef __USE_MALLOC 239 240 typedef malloc_alloc alloc; 241 typedef malloc_alloc single_client_alloc; 242 243 #else 244 245 //下面是第二级配置器 246 //主要是维护一个内存池,用来小于128byte的小型区块内存的分配 247 //其中,有多个链表,各链表中的node大小从8-128byte,都是8的倍数 248 //分配时,不是8的倍数,上调至最近的8的倍数, 249 //然后从相应链表中取下一个对应大小的node分配给请求 250 #ifdef __SUNPRO_CC 251 enum {__ALIGN = 8}; //小型区块的上调边界 252 enum {__MAX_BYTES = 128}; //小型区块的上限 253 enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; 254 #endif 255 256 //第二级配置器 257 template <bool threads, int inst> 258 class __default_alloc_template 259 { 260 private: 261 # ifndef __SUNPRO_CC 262 enum {__ALIGN = 8}; //小型区块的上调边界 263 enum {__MAX_BYTES = 128}; //小型区块的上限 264 enum {__NFREELISTS = __MAX_BYTES/__ALIGN}; 265 # endif 266 //大小上调至8的倍数 267 static size_t ROUND_UP(size_t bytes) 268 { 269 return (((bytes) + __ALIGN-1) & ~(__ALIGN - 1)); 270 } 271 __PRIVATE: 272 union obj 273 { 274 union obj * free_list_link; //用于在链表中指向下一个节点 275 char client_data[1]; //用于存储实际区块的内存地址,由于这是一个union,很好的节约了这个数据的内存 276 }; 277 private: 278 # ifdef __SUNPRO_CC 279 static obj * __VOLATILE free_list[]; 280 # else 281 static obj * __VOLATILE free_list[__NFREELISTS]; 282 # endif 283 static size_t FREELIST_INDEX(size_t bytes) 284 { 285 return (((bytes) + __ALIGN-1)/__ALIGN - 1); 286 } 287 288 //返回大小为n的对象,并可能加入大小为n的其他区块到free list 289 static void *refill(size_t n); 290 //配置一块空间,可容纳nobjs个大小为"size"的区块 291 //如果配置nobjs个区块有所不便,nobjs可能会降低 292 static char *chunk_alloc(size_t size, int &nobjs); 293 294 //chunk 分配、配置的状态 295 static char *start_free; //内存池起始位置。只在chunk_alloc()中变化 296 static char *end_free; //内存池结束位置。只在chunk_alloc()中变化 297 static size_t heap_size; 298 /* 299 # ifdef __STL_SGI_THREADS 300 static volatile unsigned long __node_allocator_lock; 301 static void __lock(volatile unsigned long *); 302 static inline void __unlock(volatile unsigned long *); 303 # endif 304 */ 305 306 # ifdef __STL_PTHREADS 307 static pthread_mutex_t __node_allocator_lock; 308 # endif 309 310 # ifdef __STL_WIN32THREADS 311 static CRITICAL_SECTION __node_allocator_lock; 312 static bool __node_allocator_lock_initialized; 313 314 public: 315 __default_alloc_template() 316 { 317 //假定第一个构造函数的调用在线程启动起 318 if (!__node_allocator_lock_initialized) 319 { 320 InitializedCriticalSection(&__node_allocator_lock); 321 __node_allocator_lock_initialized = true; 322 } 323 } 324 private: 325 # endif 326 327 class lock 328 { 329 public: 330 lock() { __NODE_ALLOCATOR_LOCK; } 331 ~lock() { __NODE_ALLOCATOR_UNLOCK; } 332 }; 333 friend class lock; 334 public: 335 //n必须大于0 336 static void * allocate(size_t n) 337 { 338 obj * __VOLATILE * my_free_list; 339 obj * __RESTRICT result; 340 341 //需要分配的大小大于二级配置器的__MAX_BYTES,直接使用第一级配置器 342 if (n > (size_t) __MAX_BYTES) 343 { 344 return(malloc_alloc::allocate(n)); 345 } 346 my_free_list = free_list + FREELIST_INDEX(n); //找到比需要分配的大小大,且最接近的大小块所在的链表所在free_list数组中的位置 347 348 //如果支持线程,定义lock 349 # ifndef _NOTHREADS 350 lock lock_instance; 351 # endif 352 result = *my_free_list; //取出找的对应链表的指向第一个节点的指针 353 if (result == 0) //对应的链表中没有剩余未分配的节点区块 354 { 355 void *r = refill(ROUND_UP(n)); //再从内存池中分配一批,需求大小的区块(实际大小是请求大小上调至8的倍数后的数值), 356 //然后,放入对应链表,待分配给请求 357 return r; 358 } 359 //如果对应大小区块的链表中不为空,还有待分配的区块,取出第一个节点 360 *my_free_list = result -> free_list_link; 361 return (result); 362 }; 363 364 //p不可以是0 365 static void deallocate(void *p, size_t n) 366 { 367 obj *q = (obj *)p; 368 obj * __VOLATILE * my_free_list; 369 370 //大于区块大小上限的,直接调用第一级配置器释放 371 if (n > (size_t) __MAX_BYTES) 372 { 373 malloc_alloc::deallocate(p, n); 374 return; 375 } 376 my_free_list = free_list + FREELIST_INDEX(n); 377 //需要修改my_free_list,如果支持线程,那么需要加上互斥锁 378 # ifndef _NOTHREADS 379 lock lock_instance; 380 # endif 381 382 //头插法,插入对应大小的区块链表 383 q -> free_list_link = *my_free_list; 384 *my_free_list = q; 385 //lock是静态对象,到此,将自动析构销毁,在其析构函数中,会释放锁 386 } 387 388 static void *reallocate(void *p, size_t old_sz, size_t new_sz); 389 390 }; 391 392 typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc; 393 typedef __default_alloc_template<false, 0> single_client_alloc; 394 395 396 // 我们从大的chunks中分配内存,是为了避免使用malloc太频繁了 397 // 假设size已经适当上调至8的倍数 398 // 我持有allocation lock 399 // 注意参数objs 是pass by reference 400 template <bool threads, int inst> 401 char * 402 __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs) 403 { 404 char * result; 405 size_t total_bytes = size * nobjs; 406 size_t bytes_left = end_free - start_free; //内存池剩余空间 407 408 if (bytes_left >= total_bytes) 409 { 410 //内存池中剩余的空间足够满足需求量 411 result = start_free; 412 start_free += total_bytes; 413 return(result); 414 } 415 else if (bytes_left >= size) 416 { 417 //内存池剩余空间不能完全满足需求量,但足够供应一个及以上的区块 418 nobjs = bytes_left/size; 419 total_bytes = size * nobjs; 420 result = start_free; 421 start_free += total_bytes; 422 return (result); 423 } 424 else 425 { 426 //内存池连一个区块的大小都无法满足 427 size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4); 428 //以下试着让内存池中的残余零头还有利用价值 429 if (bytes_left > 0) 430 { 431 //内存池中内还有一些零头,先配给适当的free list 432 //首先寻找适当的free list 433 obj * __VOLATILE * my_free_list = 434 free_list + FREELIST_INDEX(bytes_left); 435 436 //调整free list,将内存池中残余的空间编入 437 ((obj *)start_free) -> free_list_link = *my_free_list; 438 *my_free_list = (obj *)start_free; 439 } 440 441 //配置heap空间,用来补充内存池 442 start_free = (char *)malloc(bytes_to_get); 443 if (0 == start_free) 444 { 445 //如果heap空间不足,malloc()失败 446 int i; 447 obj * __VOLATILE *my_free_list, *p; 448 //试着检视我们手上的东西。这不会造成伤害。我们不打算尝试配置 449 //较小的区块,因为那在多线程机器上容易导致灾难 450 //以下搜索适当的free list 451 //所谓适当是指“尚有未用区块,且区块够大”之free list 452 for (i = size; i <= __MAX_BYTES; i += __ALIGN) 453 { 454 my_free_list = free_list + FREELIST_INDEX(i); 455 p = *my_free_list; 456 if (0 != p) 457 { 458 //free list内尚有未用区块 459 //调整free list以释放出未用的区块到内存池 460 *my_free_list = p -> free_list_link; 461 start_free = (char *)p; 462 end_free = start_free + i; 463 // 此时内存池已经有内存了 464 // 递归调用自己,为了修正objs 465 return chunk_alloc(size, nobjs); 466 //注意,任何残余的零头终将被编入适当的free list中备用 467 468 } 469 } 470 end_free = 0; //如果出现意外(山穷水尽,到处都没有内存可用了) 471 //调用第一级配置器,看看out-of-memory机制能否尽点力 472 start_free = (char *)malloc_alloc::allocate(bytes_to_get); 473 //这会导致抛出异常,或内存不足的情况获得改善 474 } 475 heap_size += bytes_to_get; 476 end_free = start_free + bytes_to_get; 477 //递归调用自己,为了修正objs 478 return chunk_alloc(size, nobjs); 479 } 480 } 481 482 483 484 // 返回一个大小为n的对象,并且有时候会为适当的free list 增加节点 485 // 假设n已经适当上调至8的倍数 486 // 我们持有allocation lock 487 template <bool threads, int inst> 488 void* __default_alloc_template<threads, inst>::refill(size_t n) 489 { 490 int nobjs = 20; //默认一次分配20个需求大小的区块 491 char * chunk = chunk_alloc(n, nobjs); //chunk是分配的空间的开始地址,令其类型为char *,主要是因为一个char的大小正好是一个byte 492 obj * __VOLATILE *my_free_list; 493 obj * result; 494 obj * current_obj, * next_obj; 495 int i; 496 497 //如果只获得一个区块,这个区块就分配给调用者,free list 无新节点 498 if (1 == nobjs) return chunk; 499 //否则准备调整free list,纳入新节点 500 my_free_list = free_list + FREELIST_INDEX(n); 501 502 //以下在chunk空间内建立free list 503 result = (obj *)chunk; //这一块准备返回给客端 504 // 以下导引free list 指向新配置的空间(取自内存池) 505 506 //由于chunk是char*,所以加上n,就表示走过n个char, 507 //一个char正好是一个byte,所以chunk+n现在指向第二个区块 508 *my_free_list = next_obj = (obj *)(chunk + n); 509 for (i = 1; ; ++i) 510 { 511 // 从1开始,因为第0个将返回给客端 512 current_obj = next_obj; 513 // 每次移动n个char,正好是n个byte,所以正好指向下个区块 514 next_obj = (obj *)((char *)next_obj + n); 515 if (nobjs - 1 == i) 516 { 517 // 已经遍历完,此时next_obj指向的内存已经超出我们分配的大小了 518 // 不属于我们的内存 519 current_obj -> free_list_link = 0; 520 break; 521 } 522 else 523 { 524 current_obj -> free_list_link = next_obj; 525 } 526 } 527 return result; 528 } 529 530 531 template<bool threads, int inst> 532 void* 533 __default_alloc_template<threads, inst>::reallocate(void *p, 534 size_t old_sz, 535 size_t new_sz) 536 { 537 void * result; 538 size_t copy_sz; 539 540 if (old_sz > (size_t) __MAX_BYTES && new_sz > (size_t) __MAX_BYTES) 541 { 542 return realloc(p, new_sz); 543 } 544 if (ROUND_UP(old_sz) == ROUND_UP(new_sz)) return p; 545 result = allocate(new_sz); 546 copy_sz = new_sz > old_sz ? old_sz : new_sz; 547 memcpy(result, p, copy_sz); 548 deallocate(p, old_sz); 549 return result; 550 551 } 552 553 #ifdef __STL_PTHREADS 554 template <bool threads,int inst> 555 pthread_mutex_t 556 __default_alloc_template<threads, inst>::__node_allocator_lock 557 = PTHREAD_MUTEX_INITIALIZER; 558 #endif 559 560 #ifdef __STL_WIN32THREADS 561 template <bool threads, int inst> CRITICAL_SECTION 562 __default_alloc_template<threads, inst>::__node_allocator_lock; 563 564 template <bool threads, int inst> bool 565 __default_alloc_template<threads, inst>::__node_allocator_lock_initialized 566 = false; 567 #endif 568 569 //省略了通用lock的实现(即不使用pthread,也没有win32thread) 570 571 template <bool threads, int inst> 572 char *__default_alloc_template<threads, inst>::start_free = 0; //设置初始值 573 574 template <bool threads, int inst> 575 char *__default_alloc_template<threads, inst>::end_free = 0; //设置初始值 576 577 template <bool threads, int inst> 578 size_t __default_alloc_template<threads, inst>::heap_size = 0; //设置初始值 579 580 //初始化16种大小的区块链表为空 581 template <bool threads, int inst> 582 typename __default_alloc_template<threads, inst>::obj * __VOLATILE 583 __default_alloc_template<threads, inst>::free_list[ 584 # ifdef __SUNPRO_CC 585 __NFREELISTS 586 # else 587 __default_alloc_template<threads, inst>::__NFREELISTS 588 # endif 589 ] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; 590 591 # ifdef __STL_WIN32THREADS 592 // Create one to get critical section initialized. 593 // We do this onece per file, but only the first constructor 594 // does anything. 595 static alloc __node_allocator_dummy_instance; 596 # endif 597 598 # endif /* ! __USE_MALLOC */ 599 600 #if defined(__sgi) && !defined(__GNUC__) && (_MIPS_SIM != _MIPS_SIM_ABI32) 601 #pragma reset woff 1174 602 #endif 603 604 __STL_END_NAMESPACE 605 606 #undef __PRIVATE 607 608 #endif /* __SGI_STL_INTERNAL_ALLOC_WJZH_H */ 609 610 //End