山寨STL实现之内存池V2
2013-01-19 20:16
225 查看
在上一篇中我们已经实现了一个简单的内存池,可以申请更大块的内存块来减少申请小块内存块时产生的内存碎片。
在本篇中,我们需要为其加入内存泄漏的检测代码,以此来检测代码编写过程中的疏忽带来的内存泄漏。(callstack的显示暂时仅支持Windows)
一、内存泄漏检测
首先,改写obj和block结构,在obj中加入一个域released表示这个chunk是否被释放
1 struct obj
2 {
3 #ifdef _DEBUG
4 bool released;
5
6 #if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) // Only windows can get callstack
7 #define CALLSTACK_MAX_DEPTH 30
8 UINT_PTR callStack[CALLSTACK_MAX_DEPTH];
9 DWORD dwCallStackDepth; // Real depth
#endif
#endif
obj* next;
};
struct block
{
block* next;
void* data;
#ifdef _DEBUG
size_type size;
#if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
UINT_PTR callStack[CALLSTACK_MAX_DEPTH];
DWORD dwCallStackDepth;
#endif
#endif
};
其中的callstack部分将在下一节中介绍
然后,我们增加一个结构
#ifdef _DEBUG
struct use
{
obj* data;
use* next;
};
#endif
其中data域指向了一块分配出去的小内存块,next域形成了一张链表。
然后,我们添加一个成员变量来保存这张链表,以及一个函数来将一个chunk插入这张链表
#ifdef _DEBUG
use* use_list;
#endif
#ifdef _DEBUG
inline void MemoryPool::addUseInfo(obj* ptr)
{
use* p = (use*)malloc(sizeof(use));
p->data = ptr;
p->next = use_list;
use_list = p;
}
#endif
然后,我们来改写refill函数使其在分配内存块时打上released标记,并将每个分配的内存块记录下来
1 void* MemoryPool::refill(int i, void(*h)(size_type))
2 {
3 const int count = 20;
4 const int preSize = (i + 1) * ALIGN + headerSize;
5 char* p = (char*)malloc(preSize * count);
6 while(p == 0)
7 {
8 h(preSize * count);
9 p = (char*)malloc(preSize * count);
}
block* pBlock = (block*)malloc(sizeof(block));
while(pBlock == 0)
{
h(sizeof(block));
pBlock = (block*)malloc(sizeof(block));
}
pBlock->data = p;
pBlock->next = free_list;
free_list = pBlock;
obj* current = (obj*)p;
#ifdef _DEBUG
addUseInfo(current);
current->released = false;
#endif
current = (obj*)((char*)current + preSize);
for(int j = 0; j < count - 1; ++j)
{
#ifdef _DEBUG
addUseInfo(current);
current->released = true;
#endif
current->next = chunk_list[i];
chunk_list[i] = current;
current = (obj*)((char*)current + preSize);
}
return (char*)p + headerSize;
}
其中的headerSize跟callstack有关,将在下一节中介绍。
当然,在deallocate时要将此内存块的released标记打为true
1 void MemoryPool::deallocate(void* p, size_type n)
2 {
3 if(p == 0) return;
4 if(n > MAX_BYTES)
5 {
6 free(p);
7 return;
8 }
9 const int i = INDEX(ROUND_UP(n));
#ifdef _DEBUG
p = (char*)p - (int)headerSize;
obj* ptr = reinterpret_cast<obj*>(p);
if (ptr->released) throw error<char*>("chunk has already released", __FILE__, __LINE__);
ptr->released = true;
#endif
reinterpret_cast<obj*>(p)->next = chunk_list[i];
chunk_list[i] = reinterpret_cast<obj*>(p);
}
OK,现在已经有模有样了,可以松口气了。接下来是最重要的部分,在MemoryPool析构时检测这个Pool内的use_list中是否有chunk的released标记为true(内存泄漏了)
1 MemoryPool::~MemoryPool()
2 {
3 #ifdef _DEBUG
4 while (use_list)
5 {
6 use *ptr = use_list, *next = use_list->next;
7 if (!ptr->data->released)
8 {
9 obj* pObj = ptr->data;
Console::SetColor(true, false, false, true);
throw error<char*>("chunk leaked", __FILE__, __LINE__);
}
free(ptr);
use_list = next;
}
#endif
clear();
}
其实说来也容易,只需要检测每个chunk的released标记是否为true就行了,而最后的clear函数是以前析构函数的代码,用来释放所有申请的block和大块的chunk。
OK,现在我们已经可以检测出没有被deallocate的chunk了。
二、callstack
首先,我们先来看一个Windows API,“CaptureStackBackTrace”这个API通过传入的一个数组来得到一组地址。当然有这个API并不够,我们还需要知道是哪个文件的第几行。“SymGetSymFromAddr64”这个API用来获取某个地址对应的函数名,“SymGetLineFromAddr64”这个API则是用来获取某个地址对应的文件名和行号的,这两个函数的32位版本则是不带64的。有了这些Windows API,我们就可以很轻松的获取到当前函数的调用堆栈了,主要的功劳还是要归功于Windows强大的dbghelp。
最后,完整的代码你可以在http://code.google.com/p/qlanguage/中找到。
在本篇中,我们需要为其加入内存泄漏的检测代码,以此来检测代码编写过程中的疏忽带来的内存泄漏。(callstack的显示暂时仅支持Windows)
一、内存泄漏检测
首先,改写obj和block结构,在obj中加入一个域released表示这个chunk是否被释放
1 struct obj
2 {
3 #ifdef _DEBUG
4 bool released;
5
6 #if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__) // Only windows can get callstack
7 #define CALLSTACK_MAX_DEPTH 30
8 UINT_PTR callStack[CALLSTACK_MAX_DEPTH];
9 DWORD dwCallStackDepth; // Real depth
#endif
#endif
obj* next;
};
struct block
{
block* next;
void* data;
#ifdef _DEBUG
size_type size;
#if defined(WIN32) && !defined(__MINGW32__) && !defined(__CYGWIN__)
UINT_PTR callStack[CALLSTACK_MAX_DEPTH];
DWORD dwCallStackDepth;
#endif
#endif
};
其中的callstack部分将在下一节中介绍
然后,我们增加一个结构
#ifdef _DEBUG
struct use
{
obj* data;
use* next;
};
#endif
其中data域指向了一块分配出去的小内存块,next域形成了一张链表。
然后,我们添加一个成员变量来保存这张链表,以及一个函数来将一个chunk插入这张链表
#ifdef _DEBUG
use* use_list;
#endif
#ifdef _DEBUG
inline void MemoryPool::addUseInfo(obj* ptr)
{
use* p = (use*)malloc(sizeof(use));
p->data = ptr;
p->next = use_list;
use_list = p;
}
#endif
然后,我们来改写refill函数使其在分配内存块时打上released标记,并将每个分配的内存块记录下来
1 void* MemoryPool::refill(int i, void(*h)(size_type))
2 {
3 const int count = 20;
4 const int preSize = (i + 1) * ALIGN + headerSize;
5 char* p = (char*)malloc(preSize * count);
6 while(p == 0)
7 {
8 h(preSize * count);
9 p = (char*)malloc(preSize * count);
}
block* pBlock = (block*)malloc(sizeof(block));
while(pBlock == 0)
{
h(sizeof(block));
pBlock = (block*)malloc(sizeof(block));
}
pBlock->data = p;
pBlock->next = free_list;
free_list = pBlock;
obj* current = (obj*)p;
#ifdef _DEBUG
addUseInfo(current);
current->released = false;
#endif
current = (obj*)((char*)current + preSize);
for(int j = 0; j < count - 1; ++j)
{
#ifdef _DEBUG
addUseInfo(current);
current->released = true;
#endif
current->next = chunk_list[i];
chunk_list[i] = current;
current = (obj*)((char*)current + preSize);
}
return (char*)p + headerSize;
}
其中的headerSize跟callstack有关,将在下一节中介绍。
当然,在deallocate时要将此内存块的released标记打为true
1 void MemoryPool::deallocate(void* p, size_type n)
2 {
3 if(p == 0) return;
4 if(n > MAX_BYTES)
5 {
6 free(p);
7 return;
8 }
9 const int i = INDEX(ROUND_UP(n));
#ifdef _DEBUG
p = (char*)p - (int)headerSize;
obj* ptr = reinterpret_cast<obj*>(p);
if (ptr->released) throw error<char*>("chunk has already released", __FILE__, __LINE__);
ptr->released = true;
#endif
reinterpret_cast<obj*>(p)->next = chunk_list[i];
chunk_list[i] = reinterpret_cast<obj*>(p);
}
OK,现在已经有模有样了,可以松口气了。接下来是最重要的部分,在MemoryPool析构时检测这个Pool内的use_list中是否有chunk的released标记为true(内存泄漏了)
1 MemoryPool::~MemoryPool()
2 {
3 #ifdef _DEBUG
4 while (use_list)
5 {
6 use *ptr = use_list, *next = use_list->next;
7 if (!ptr->data->released)
8 {
9 obj* pObj = ptr->data;
Console::SetColor(true, false, false, true);
throw error<char*>("chunk leaked", __FILE__, __LINE__);
}
free(ptr);
use_list = next;
}
#endif
clear();
}
其实说来也容易,只需要检测每个chunk的released标记是否为true就行了,而最后的clear函数是以前析构函数的代码,用来释放所有申请的block和大块的chunk。
OK,现在我们已经可以检测出没有被deallocate的chunk了。
二、callstack
首先,我们先来看一个Windows API,“CaptureStackBackTrace”这个API通过传入的一个数组来得到一组地址。当然有这个API并不够,我们还需要知道是哪个文件的第几行。“SymGetSymFromAddr64”这个API用来获取某个地址对应的函数名,“SymGetLineFromAddr64”这个API则是用来获取某个地址对应的文件名和行号的,这两个函数的32位版本则是不带64的。有了这些Windows API,我们就可以很轻松的获取到当前函数的调用堆栈了,主要的功劳还是要归功于Windows强大的dbghelp。
最后,完整的代码你可以在http://code.google.com/p/qlanguage/中找到。
相关文章推荐
- 内存池,参考sgi stl的实现,测试后感觉还不错
- SGI STL中内存池的实现
- vs的STL没有实现内存池
- 山寨STL实现之list
- 实现内存池MemoryPool(加了互斥锁的内存池实现:pthread_mutex_t + m_memory_pool)
- 内存池的实现
- 集合和数组保存对象的引用变量(区别C++的STL实现)
- stl实现结构体排序关键语法要点(sort)
- C++的标准模板库STL中实现的数据结构之顺序表vector的分析与使用
- STL sort函数的内部实现
- 模拟实现STL下的list容器
- STL 简单 copy 算法的实现
- C 内存池的实现
- Nova API的实现——V2 API上添加新资源
- 红黑树实现——STL中的map
- 用List模拟实现STL下的queue队列
- 一个内存池C++类的实现
- 【转】STL容器的实现原理
- STL之numeric实现
- 数据结构栈之括号匹配(STL实现&&数组栈实现)