SGI STL 学习笔记四 内存管理
2011-01-17 20:56 curer 阅读(992) 评论(1) 编辑 收藏 举报SGI STL 在g++中默认的编译选项是构造2个分配器。
第一级分配器__malloc_alloc_template
这个一级分配器设计比较简单。由于SGI STL中分配内存没有使用C++推荐的 operator new/delete 而是使用malloc/delete。所以,并没有set_new_handler()。当面对内存不足的情况,这里模仿了c++的做法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | template < int __inst> void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0; static void (* __set_malloc_handler( void (*__f)()))() { void (* __old)() = __malloc_alloc_oom_handler; __malloc_alloc_oom_handler = __f; return (__old); } template < int __inst> void * __malloc_alloc_template<__inst>::_S_oom_malloc( size_t __n) { void (* __my_malloc_handler)(); void * __result; for (;;) { //这里不断的调用处理分配不足的情况代码,如果有可能解决问题,那么OK,如果还是不行,那么 //只能抛出异常,当然,如果没有指定,默认为NULL,则会直接抛出异常。 __my_malloc_handler = __malloc_alloc_oom_handler; if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; } (*__my_malloc_handler)(); __result = malloc (__n); if (__result) return (__result); } } //这里,如果分配不足,则会调用_S_oom_malloc来救急。oom,就是out of memory的意思。 static void * allocate( size_t __n) { void * __result = malloc (__n); if (0 == __result) __result = _S_oom_malloc(__n); return __result; } |
二级分配器 __default_alloc_template
二级配置器多了很多机制,在分配小的内存上做了优化。
粗略的分配策略。
- 分配大小超过 _MAX_BYTES = 128bytes,使用一级分配器处理。当分配器大小小于128bytes时,则通过内存池管理。
- 调整分配大小到8的倍数,从freeList中,分配内存。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | template < bool threads, int inst> class __default_alloc_template { private : // Really we should use static const int x = N // instead of enum { x = N }, but few compilers accept the former. # ifndef __SUNPRO_CC enum {_ALIGN = 8}; enum {_MAX_BYTES = 128}; enum {_NFREELISTS = _MAX_BYTES/_ALIGN}; //free_list 个数 # endif static size_t _S_round_up( size_t __bytes) { return (((__bytes) + _ALIGN-1) & ~(_ALIGN - 1)); } __PRIVATE: union _Obj { union _Obj* _M_free_list_link; char _M_client_data[1]; /* The client sees this. */ }; //根据大小,找到对应的index,从1开始。 static size_t _S_freelist_index( size_t __bytes) { return (((__bytes) + _ALIGN-1)/_ALIGN - 1); } |
可以看出,_Obj就是一个简单的单向链表,只是这个链表和我们之前学习的不一样。之前的链表数据只是链表节点的一部分,而这里当分配给client的时候是一整块的。
分配空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | static void * allocate( size_t __n) { _Obj* __VOLATILE* __my_free_list; _Obj* __RESTRICT __result; if (__n > ( size_t ) _MAX_BYTES) { return (malloc_alloc::allocate(__n)); } __my_free_list = _S_free_list + _S_freelist_index(__n); // Acquire the lock here with a constructor call. // This ensures that it is released in exit or during stack // unwinding. #ifndef _NOTHREADS /*REFERENCED*/ _Lock __lock_instance; #endif __result = *__my_free_list; if (__result == 0) { //没有找到freeList void * __r = _S_refill(_S_round_up(__n)); //这里重新填充 freeList return __r; } *__my_free_list = __result -> _M_free_list_link; return (__result); }; |
如果能够获得freeList,情况很简单,将__my_free_list 的值,指向已经分配空间的下一块空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | /* Returns an object of size __n, and optionally adds to size __n free list.*/ /* We assume that __n is properly aligned. */ /* We hold the allocation lock. */ template < bool __threads, int __inst> void * __default_alloc_template<__threads, __inst>::_S_refill( size_t __n) { int __nobjs = 20; //由他来分配空间,第二个参数为引用, 所以有可能出现分配不足,也就是__nobjs < 20的情况。 char * __chunk = _S_chunk_alloc(__n, __nobjs); _Obj* __VOLATILE* __my_free_list; _Obj* __result; _Obj* __current_obj; _Obj* __next_obj; int __i; if (1 == __nobjs) return (__chunk); //如果只能分配一个大小,那么我们不需要调整freeList了。 __my_free_list = _S_free_list + _S_freelist_index(__n); /* Build free list in chunk */ //构造freeList __result = (_Obj*)__chunk; *__my_free_list = __next_obj = (_Obj*)(__chunk + __n); for (__i = 1; ; __i++) { //从第二个开始构造freeList,因为第一个需要传给上层函数。 __current_obj = __next_obj; __next_obj = (_Obj*)(( char *)__next_obj + __n); if (__nobjs - 1 == __i) { __current_obj -> _M_free_list_link = 0; break ; } else { __current_obj -> _M_free_list_link = __next_obj; } } //这里,我们发现,这个freeList,其实就是一个简单的单向链表, //__my_free_list = _S_free_list + _S_freelist_index(__n); 这个就是获得这个链表的表头。 return (__result); } |
释放空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | /* __p may not be 0 */ static void deallocate( void * __p, size_t __n) { _Obj* __q = (_Obj*)__p; _Obj* __VOLATILE* __my_free_list; if (__n > ( size_t ) _MAX_BYTES) { malloc_alloc::deallocate(__p, __n); //过大的block,我们通过1级分配器搞定 return ; } __my_free_list = _S_free_list + _S_freelist_index(__n); // acquire lock # ifndef _NOTHREADS /*REFERENCED*/ _Lock __lock_instance; # endif /* _NOTHREADS */ //这里是典型的在listHead 的下一个位置添加的操作,这里看出,对于小的block,我们并没有给操作系统,而是 //链表保存起来。 __q -> _M_free_list_link = *__my_free_list; *__my_free_list = __q; // lock is released here } |
内存池分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | /* We allocate memory in large chunks in order to avoid fragmenting */ /* the malloc heap too much. */ /* We assume that size is properly aligned. */ /* We hold the allocation lock. */ template < bool __threads, int __inst> char * __default_alloc_template<__threads, __inst>::_S_chunk_alloc( size_t __size, int & __nobjs) { char * __result; size_t __total_bytes = __size * __nobjs; size_t __bytes_left = _S_end_free - _S_start_free; if (__bytes_left >= __total_bytes) { //内存池足够,分配后返回 __result = _S_start_free; _S_start_free += __total_bytes; return (__result); } else if (__bytes_left >= __size) { //内存池不够整个memory block,但是足够一个以上的block。 __nobjs = ( int )(__bytes_left/__size); __total_bytes = __size * __nobjs; __result = _S_start_free; _S_start_free += __total_bytes; return (__result); } else { //内存池已经一个都不能满足memory block size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4); // Try to make use of the left-over piece. if (__bytes_left > 0) { //内存池中还有剩余,找到这部分空间并填入相应的memory block list中。 _Obj* __VOLATILE* __my_free_list = _S_free_list + _S_freelist_index(__bytes_left); ((_Obj*)_S_start_free) -> _M_free_list_link = *__my_free_list; *__my_free_list = (_Obj*)_S_start_free; } //内存池为空,我们从heap中找内存 //__bytes_to_get是一个不断增加的数字,也就是每次从heap分配的空间越来越多。 _S_start_free = ( char *) malloc (__bytes_to_get); if (0 == _S_start_free) { //极端情况, heap的空间不足。 size_t __i; _Obj* __VOLATILE* __my_free_list; _Obj* __p; // Try to make do with what we have. That can't // hurt. We do not try smaller requests, since that tends // to result in disaster on multi-process machines. //这部分不理解,为什么只能从大的block中分配呢? for (__i = __size; __i <= _MAX_BYTES; __i += _ALIGN) { __my_free_list = _S_free_list + _S_freelist_index(__i); __p = *__my_free_list; if (0 != __p) { //在freeList 中,我们找到了一个大的block,并且里面有数据 *__my_free_list = __p -> _M_free_list_link; _S_start_free = ( char *)__p; _S_end_free = _S_start_free + __i; //从大的freelist中,我们分配出一个来到内存池,然后递归。这次没有 //意外,会找到足够的内存 return (_S_chunk_alloc(__size, __nobjs)); // Any leftover piece will eventually make it to the // right free list. } } //还是没有满足要求,调用一级分配器看oom机制,是否能够帮助我们 _S_end_free = 0; // In case of exception. _S_start_free = ( char *)malloc_alloc::allocate(__bytes_to_get); // This should either throw an // exception or remedy the situation. Thus we assume it // succeeded. } //扩充了内存池大小 _S_heap_size += __bytes_to_get; _S_end_free = _S_start_free + __bytes_to_get; return (_S_chunk_alloc(__size, __nobjs)); //根据新的内存池大小,修正__nobjs } //end else //这里我们看出了,为什么是8的倍数,所以,我们分配的大的block,肯定会在小的block中找到位置,而不会 //浪费掉当然,具体的理由肯定还有更多,需要去了解更多的内存方面的知识才能理解。 } |
内存管理这里仅仅是一个最最基本的梳理,事实上其实仅仅是照本宣科而已,因为这里面有太多的细节需要去琢磨。STL设计这样的原因是什么?他的分配回收机制为什么是这样?他的分配粒度,以及处理分配不足的手段。对我来说都是未知,不过好在目前的确不需要思考过多这些问题。仅仅是上层的封装和简单功能的实现就已经让我受益匪浅了。这些问题还是留给时间去沉淀这些未知吧。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?