Blender内存管理库(bf_intern_guardedalloc)
Blender项目主要由c/C++编写的,模块较多,规模很大。由于C/C++语言很容易出现内存泄漏,为此Blender在底层代码中提供了内存泄漏检测机制,方便在需要时进入调试模式报告内存泄漏。
为实现内存泄漏检测,必须在内存分配时做些额外的记录,为此,提供了一组函数替换c/c++语言原生的内存分配函数。
额外的内存泄漏检测必然将影响程序的工作性能,解决的办法是,Blender提供了二组内存分配方法,一套提供额外的内存泄漏检测,用于调试,便于发现内存问题,一套用于正式工作,不影响性能。
为便于二套内存分配方法切换,Blender定义了一组内存分配指针函数,替换c/C++语言原生的内存分配函数来实现内存泄漏检测功能,代码(位于mallocn.c文件中)如下:
查看代码/* NOTE: Keep in sync with MEM_use_lockfree_allocator(). */
size_t (*MEM_allocN_len)(const void *vmemh) = MEM_lockfree_allocN_len;
void (*MEM_freeN)(void *vmemh) = MEM_lockfree_freeN;
void *(*MEM_dupallocN)(const void *vmemh) = MEM_lockfree_dupallocN;
void *(*MEM_reallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_reallocN_id;
void *(*MEM_recallocN_id)(void *vmemh, size_t len, const char *str) = MEM_lockfree_recallocN_id;
void *(*MEM_callocN)(size_t len, const char *str) = MEM_lockfree_callocN;
void *(*MEM_calloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_calloc_arrayN;
void *(*MEM_mallocN)(size_t len, const char *str) = MEM_lockfree_mallocN;
void *(*MEM_malloc_arrayN)(size_t len, size_t size, const char *str) = MEM_lockfree_malloc_arrayN;
void *(*MEM_mallocN_aligned)(size_t len,
size_t alignment,
const char *str) = MEM_lockfree_mallocN_aligned;
void (*MEM_printmemlist_pydict)(void) = MEM_lockfree_printmemlist_pydict;
void (*MEM_printmemlist)(void) = MEM_lockfree_printmemlist;
void (*MEM_callbackmemlist)(void (*func)(void *)) = MEM_lockfree_callbackmemlist;
void (*MEM_printmemlist_stats)(void) = MEM_lockfree_printmemlist_stats;
void (*MEM_set_error_callback)(void (*func)(const char *)) = MEM_lockfree_set_error_callback;
bool (*MEM_consistency_check)(void) = MEM_lockfree_consistency_check;
void (*MEM_set_memory_debug)(void) = MEM_lockfree_set_memory_debug;
size_t (*MEM_get_memory_in_use)(void) = MEM_lockfree_get_memory_in_use;
unsigned int (*MEM_get_memory_blocks_in_use)(void) = MEM_lockfree_get_memory_blocks_in_use;
void (*MEM_reset_peak_memory)(void) = MEM_lockfree_reset_peak_memory;
size_t (*MEM_get_peak_memory)(void) = MEM_lockfree_get_peak_memory;
#ifndef NDEBUG
const char *(*MEM_name_ptr)(void *vmemh) = MEM_lockfree_name_ptr;
#endif
MEM_guardedalloc.h中定义了相关内存分配函数,均为指针函数,通过指针函数赋值,来实现二种机制的切换。二套函数具体实现分别们于mallocn_lockfree_impl.c和mallocn_guarded_impl.c文件中,整合在bf_intern_guardedalloc静态库中。
mallocn.c中实现函数指针被赋值,默认使用mallocn_lockfree_impl.c中的函数,如果需要使用mallocn_guarded_impl.c中的函数,只需要调用MEM_use_guarded_allocator函数,实现二套机制的切换。
在blender主程序文件中createor.c中提供了选择,仅当运行参数中包含调试选项时时使用保护(guarded)模式内存分配,以方便理准确定位内存问题
1 /* NOTE: Special exception for guarded allocator type switch: 2 * we need to perform switch from lock-free to fully 3 * guarded allocator before any allocation happened. 4 */ 5 { 6 int i; 7 for (i = 0; i < argc; i++) { 8 if (STR_ELEM(argv[i], "-d", "--debug", "--debug-memory", "--debug-all")) { 9 printf("Switching to fully guarded memory allocator.\n"); 10 MEM_use_guarded_allocator(); 11 break; 12 } 13 else if (STREQ(argv[i], "--")) { 14 break; 15 } 16 } 17 MEM_initialize_memleak_detection(); 18 }
同时在第17行调用MEM_initialize_memleak_detection()函数实例化MemLeakPrinter(leak_detector.cc中定义)类到静态变量中,用于初始内存泄漏检测。该类只定义了析构函数,因此在程序结束时自动释放时调用析构函数,实现打印内存泄漏信息。也就无需主动调用,正发布版程序中也无需处理,不用担心性能。
例如:MEM_guardedalloc.h中定义的函数指针:extern void *(*MEM_callocN)(size_t len, const char *str) ,在mallocn.c中将它赋值
void *(*MEM_callocN)(size_t len, const char *str) = MEM_guarded_callocN;
MME_lockfree_callocN函数定义于mallocn_guarded_impl.c文件中,如下:
1 void *MEM_guarded_mallocN(size_t len, const char *str) 2 { 3 MemHead *memh; 4 5 len = SIZET_ALIGN_4(len); 6 7 memh = (MemHead *)malloc(len + sizeof(MemHead) + sizeof(MemTail)); 8 9 if (LIKELY(memh)) { 10 make_memhead_header(memh, len, str); 11 if (UNLIKELY(malloc_debug_memset && len)) { 12 memset(memh + 1, 255, len); 13 } 14 15 #ifdef DEBUG_MEMCOUNTER 16 if (_mallocn_count == DEBUG_MEMCOUNTER_ERROR_VAL) 17 memcount_raise(__func__); 18 memh->_count = _mallocn_count++; 19 #endif 20 return (++memh); 21 } 22 print_error("Malloc returns null: len=" SIZET_FORMAT " in %s, total %u\n", 23 SIZET_ARG(len), 24 str, 25 (unsigned int)mem_in_use); 26 return NULL; 27 }
通过代码分析可知,内存分配的保护措施就是,在请求分配内存块时,在前面额外增加了一个MemHead和MemTail,它存储了请求的内存块大小,同时更新totblock,mem_in_use,peak_mem三个与内存保护相关变量
以下是Mem_freeN指向的函数MEM_gurded_freeN(它与Mem_allocN相对应,用于释放内存)
1 void MEM_guarded_freeN(void *vmemh) 2 { 3 MemTail *memt; 4 MemHead *memh = vmemh; 5 const char *name; 6 7 if (memh == NULL) { 8 MemorY_ErroR("free", "attempt to free NULL pointer"); 9 /* print_error(err_stream, "%d\n", (memh+4000)->tag1); */ 10 return; 11 } 12 13 if (sizeof(intptr_t) == 8) { 14 if (((intptr_t)memh) & 0x7) { 15 MemorY_ErroR("free", "attempt to free illegal pointer"); 16 return; 17 } 18 } 19 else { 20 if (((intptr_t)memh) & 0x3) { 21 MemorY_ErroR("free", "attempt to free illegal pointer"); 22 return; 23 } 24 } 25 26 memh--; 27 if (memh->tag1 == MEMFREE && memh->tag2 == MEMFREE) { 28 MemorY_ErroR(memh->name, "double free"); 29 return; 30 } 31 32 if ((memh->tag1 == MEMTAG1) && (memh->tag2 == MEMTAG2) && ((memh->len & 0x3) == 0)) { 33 memt = (MemTail *)(((char *)memh) + sizeof(MemHead) + memh->len); 34 if (memt->tag3 == MEMTAG3) { 35 36 if (leak_detector_has_run) { 37 MemorY_ErroR(memh->name, free_after_leak_detection_message); 38 } 39 40 memh->tag1 = MEMFREE; 41 memh->tag2 = MEMFREE; 42 memt->tag3 = MEMFREE; 43 /* after tags !!! */ 44 rem_memblock(memh); 45 46 return; 47 } 48 MemorY_ErroR(memh->name, "end corrupt"); 49 name = check_memlist(memh); 50 if (name != NULL) { 51 if (name != memh->name) { 52 MemorY_ErroR(name, "is also corrupt"); 53 } 54 } 55 } 56 else { 57 mem_lock_thread(); 58 name = check_memlist(memh); 59 mem_unlock_thread(); 60 if (name == NULL) { 61 MemorY_ErroR("free", "pointer not in memlist"); 62 } 63 else { 64 MemorY_ErroR(name, "error in header"); 65 } 66 } 67 68 totblock--; 69 /* here a DUMP should happen */ 70 }
只要通过该库中函数分配的内存就能通过设置启动参数,在程序结束时显示内存泄漏等相关情况。