C++ 内存泄露检测小工具

前言

内存泄露一直是 C++ 的一个痛点,内存泄露的检测和定位是一件非常麻烦的事,这个小工具通过重载 newdelete 来检测内存泄露,但由于种种问题,难以做到跨文件,所以实际意义并不大。

单文件的内存泄露通常很容易发现和定位,该小工具基本只能作为玩具轮子做着玩,实际上基本用不到,但写都写了,权当水一篇文章了。

为什么无法跨文件

由于重载的是全局的 newdelete 函数,在跨文件时必然会遇到 ODR 问题,所以无论以什么方式重载,都没法做到跨文件,多个编译单元的链接总会出问题。

如果重载的不是全局 newdelete,那么就只能重载类内的 operator newoperator delete,这就失去普适性了,只能针对类做内存泄漏检测,也无法检测基本类型。

除非和 Java 一样,所有类都继承一个类,通过追踪类的 newdelete 来实现内存泄露检测,这是另一个大工程,就不展开了。

总之无法做到简简单单就能够解决内存泄露问题。

功能展示

#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 时删除,如果出现不匹配的情况,则有内存泄露发生,很好理解,但在代码层面实现起来还是有一些困难。因为重载 newdelete 之后会有递归调用的问题,所以只能回归到 mallocfree

另外,为了编码方便,网上的内存泄露检测工具通常是用链表实现,但是个人觉得很不爽,这种配对问题显然哈希表效率更高,可能是考虑到哈希表编码的困难程度已经超过实现这个工具,所以网上没找到哈希表实现的内存泄露检测。

我这里就简单写了个哈希表,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条评论