C++ 内存泄露检测小工具
前言
内存泄露一直是 C++ 的一个痛点,内存泄露的检测和定位是一件非常麻烦的事,这个小工具通过重载 new
和 delete
来检测内存泄露,但由于种种问题,难以做到跨文件,所以实际意义并不大。
单文件的内存泄露通常很容易发现和定位,该小工具基本只能作为玩具轮子做着玩,实际上基本用不到,但写都写了,权当水一篇文章了。
为什么无法跨文件
由于重载的是全局的 new
和 delete
函数,在跨文件时必然会遇到 ODR 问题,所以无论以什么方式重载,都没法做到跨文件,多个编译单元的链接总会出问题。
如果重载的不是全局 new
和 delete
,那么就只能重载类内的 operator new
和 operator delete
,这就失去普适性了,只能针对类做内存泄漏检测,也无法检测基本类型。
除非和 Java 一样,所有类都继承一个类,通过追踪类的 new
和 delete
来实现内存泄露检测,这是另一个大工程,就不展开了。
总之无法做到简简单单就能够解决内存泄露问题。
功能展示
#include "leak_detector.h"
using namespace std;
int main() {
int* double_delete = new int(2);
delete double_delete;
delete double_delete;
return 0;
}
// output:
// [leak_detector] Delete a non-exist pointer(0xfb2490) in main.cpp at line 10
//----------------------------------------------------------------------------
#include "leak_detector.h"
using namespace std;
int main() {
int* forget_delete = new int(2);
return 0;
}
// output:
// [leak_detector] Undeleted pointer(0x27e2490) in main.cpp at line 8
代码实现
这部分的原理就不多说了,主要是在 new
时记录一条信息,delete
时删除,如果出现不匹配的情况,则有内存泄露发生,很好理解,但在代码层面实现起来还是有一些困难。因为重载 new
和 delete
之后会有递归调用的问题,所以只能回归到 malloc
和 free
另外,为了编码方便,网上的内存泄露检测工具通常是用链表实现,但是个人觉得很不爽,这种配对问题显然哈希表效率更高,可能是考虑到哈希表编码的困难程度已经超过实现这个工具,所以网上没找到哈希表实现的内存泄露检测。
我这里就简单写了个哈希表,200+行的代码,哈希表的实现就占了 100+ 行,还是个残疾版的哈希表,总之能用就行,但不得不说,哈希表的实现反而是更有难度的。
#pragma once
#include <iostream>
#include <sstream>
// Console color settings
#ifdef _WIN32
#include "Windows.h"
#endif
#ifdef linux
#define LEAK_DETECTOR_RED "\033[0;32;31m"
#define LEAK_DETECTOR_NONE "\033[m"
#endif
namespace leak_detector {
class AllocateRecord {
public:
AllocateRecord(const char* file, int line) : file(file), line(line) {}
~AllocateRecord() = default;
std::pair<const char*, const int> getInfo() { return {file, line}; }
private:
const char* file;
const int line;
};
class SimpleHash {
using iterator = std::pair<void*, AllocateRecord*>*;
using value = std::pair<void*, AllocateRecord*>*;
public:
SimpleHash() {
m_data = (value*)malloc(sizeof(value) * m_cap);
memset(m_data, 0, sizeof(value) * m_cap);
}
~SimpleHash() {
for(size_t i = 0; i < m_cap; ++i) {
if(m_data[i] != nullptr) {
free(m_data[i]);
}
}
free(m_data);
}
iterator* begin() {
return m_data + m_begin;
}
iterator* end() {
return m_data + m_end + 1;
}
iterator* next(iterator* iter) {
auto end = SimpleHash::end();
while(iter < end) {
++iter;
if(*iter != nullptr) {
return iter;
}
}
return end;
}
value operator[](void* p) {
size_t index = hash(p);
if(m_data[index] == nullptr) {
return nullptr;
} else {
while(m_data[index]->first != p) {
index++;
if(index == m_cap) {
index = 0;
} else if(index == hash(p)) {
return nullptr;
}
}
}
return m_data[index];
}
void insert(value val) {
if(m_size > m_cap / 2) {
reserve(m_cap * 2);
}
m_size++;
size_t index = hash(val->first);
if(m_data[index] != nullptr) {
while(true) {
index++;
if(index == m_cap) {
index = -1;
} else if(m_data[index] == nullptr) {
break;
}
}
}
m_data[index] = val;
if(index < m_begin) {
m_begin = index;
}
if(index > m_end) {
m_end = index;
}
}
void reserve(size_t size) {
value* temp = m_data;
size_t old_cap = m_cap;
m_begin = ULONG_MAX;
m_end = 0;
m_cap = size;
m_size = 0;
m_data = (value*)malloc(sizeof(value) * m_cap);
memset(m_data, 0, sizeof(value) * m_cap);
for(size_t i = 0; i < old_cap; ++i) {
if(temp[i] != nullptr) {
insert(temp[i]);
}
}
free(temp);
}
iterator* find(void* ptr) {
auto end = SimpleHash::end();
for(size_t i = 0; i < m_cap; ++i) {
if(&m_data[i] < end && m_data[i] != nullptr && m_data[i]->first == ptr) {
return m_data + i;
}
}
return end;
}
void erase(void* ptr) {
auto iter = find(ptr);
if(iter != end()) {
(*iter)->second->~AllocateRecord();
free((*iter)->second);
*iter = nullptr;
m_size--;
}
}
private:
size_t hash(void* ptr) {
unsigned long long i = (unsigned long long)ptr;
return i % m_cap;
}
size_t m_size = 0;
size_t m_cap = 4;
size_t m_begin = ULONG_MAX;
size_t m_end = 0;
value* m_data;
};
class LeakDetector {
public:
~LeakDetector() {
m_print.clear();
auto begin = mp.begin(), end = mp.end();
while(begin < end) {
std::stringstream ss;
ss << (*begin)->first;
std::string addr = ss.str();
m_print += "[leak_detector] Undeleted pointer(" + addr + ") in "
+ std::string((*begin)->second->getInfo().first) + " at line "
+ std::to_string((*begin)->second->getInfo().second) + "\n";
begin = mp.next(begin);
}
print();
}
static LeakDetector& instance() {
static LeakDetector instance;
return instance;
}
void allocateElement(void* ptr, const char* file, int line) {
mp.insert(new std::pair<void*, AllocateRecord*>{ptr, new AllocateRecord(file, line)});
}
bool deleteElement(void* ptr) {
if(mp.find(ptr) != mp.end()) {
mp.erase(ptr);
return true;
} else {
std::stringstream ss;
ss << ptr;
std::string addr = ss.str();
m_print = "[leak_detector] Delete a non-exist pointer(" + addr + ") in "
+ std::string(m_file) + " at line " + std::to_string(m_line);
print();
return false;
}
}
void setInfo(std::string file, int line) {
m_file = file;
m_line = line;
}
private:
SimpleHash mp;
std::string m_file;
int m_line;
std::string m_print;
void print(){
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x04);
#endif
#ifdef linux
std::cout << LEAK_DETECTOR_RED;
#endif
std::cout << m_print << std::endl;
#ifdef _WIN32
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),0x07);
#endif
#ifdef linux
std::cout << LEAK_DETECTOR_NONE;
#endif
}
};
} // leak_detector
void* operator new(size_t size, const char* file, int line) {
void* p = malloc(size);
leak_detector::LeakDetector::instance().allocateElement(p, file, line);
return p;
}
void* operator new[](size_t size, const char* file, int line) {
void* p = malloc(size);
leak_detector::LeakDetector::instance().allocateElement(p, file, line);
return p;
}
void operator delete(void* ptr) noexcept {
if(leak_detector::LeakDetector::instance().deleteElement(ptr)) {
free(ptr);
}
}
void operator delete(void* ptr, size_t) noexcept {
if(leak_detector::LeakDetector::instance().deleteElement(ptr)) {
free(ptr);
}
}
void operator delete[](void* ptr) noexcept {
if(leak_detector::LeakDetector::instance().deleteElement(ptr)) {
free(ptr);
}
}
void operator delete[](void* ptr, size_t) noexcept {
if(leak_detector::LeakDetector::instance().deleteElement(ptr)) {
free(ptr);
}
}
#define new new (__FILE__, __LINE__)
#define delete leak_detector::LeakDetector::instance().setInfo(__FILE__, __LINE__);delete
评论 |
0条评论