批判我的堆调试器

时间:2010-05-13 20:59:10

标签: c++ debugging heap

我编写了以下堆调试器,以便向初级程序员演示内存泄漏,双删除和错误形式的删除(即尝试删除delete p而不是delete[] p的数组。 / p>

我很乐意从强大的C ++程序员那里得到一些反馈,因为我以前从未这样做过,我确信我做过一些愚蠢的错误。谢谢!

#include <cstdlib>
#include <iostream>
#include <new>

namespace
{
    const int ALIGNMENT = 16;
    const char* const ERR = "*** ERROR: ";
    int counter = 0;

    struct heap_debugger
    {
        heap_debugger()
        {
            std::cerr << "*** heap debugger started\n";
        }

        ~heap_debugger()
        {
            std::cerr << "*** heap debugger shutting down\n";
            if (counter > 0)
            {
                std::cerr << ERR << "failed to release memory " << counter << " times\n";
            }
            else if (counter < 0)
            {
                std::cerr << ERR << (-counter) << " double deletes detected\n";
            }
        }
    } instance;

    void* allocate(size_t size, const char* kind_of_memory, size_t token) throw (std::bad_alloc)
    {
        void* raw = malloc(size + ALIGNMENT);
        if (raw == 0) throw std::bad_alloc();

        *static_cast<size_t*>(raw) = token;
        void* payload = static_cast<char*>(raw) + ALIGNMENT;

        ++counter;
        std::cerr << "*** allocated " << kind_of_memory << " at " << payload << " (" << size << " bytes)\n";
        return payload;
    }

    void release(void* payload, const char* kind_of_memory, size_t correct_token, size_t wrong_token) throw ()
    {
        if (payload == 0) return;

        std::cerr << "*** releasing " << kind_of_memory << " at " << payload << '\n';
        --counter;

        void* raw = static_cast<char*>(payload) - ALIGNMENT;
        size_t* token = static_cast<size_t*>(raw);

        if (*token == correct_token)
        {
            *token = 0xDEADBEEF;
            free(raw);
        }
        else if (*token == wrong_token)
        {
            *token = 0x177E6A7;
            std::cerr << ERR << "wrong form of delete\n";
        }
        else
        {
            std::cerr << ERR << "double delete\n";
        }
    }
}

void* operator new(size_t size) throw (std::bad_alloc)
{
    return allocate(size, "non-array memory", 0x5AFE6A8D);
}

void* operator new[](size_t size) throw (std::bad_alloc)
{
    return allocate(size, "    array memory", 0x5AFE6A8E);
}

void operator delete(void* payload) throw ()
{
    release(payload, "non-array memory", 0x5AFE6A8D, 0x5AFE6A8E);
}

void operator delete[](void* payload) throw ()
{
    release(payload, "    array memory", 0x5AFE6A8E, 0x5AFE6A8D);
}

5 个答案:

答案 0 :(得分:4)

您可以保留所有分配的列表,而不是进行侵入式记录保存。然后,您可以在不破坏自己的数据的情况下释放内存,并跟踪特定地址被“删除”的次数,还可以找到程序尝试删除不匹配地址(即不在列表中)的位置。

答案 1 :(得分:2)

这是一个非常好的开始。当你要求反馈时,这是我的2美分:

  1. 代码将跟踪信息写入cerr,这实际上是出于错误。使用cout作为信息日志。
  2. 对齐量是任意的。如果代码试图分配4090个字节,你将分配4106,它会溢出到下一个4k块,这是一个内存页面的大小。计算出的对齐值会更好......或者将ALIGNMENT重命名为HEADER_SIZE或类似的东西。
  3. 鉴于您正在创建的标题,您可以在分配时存储“种类内存”的大小和标记,并在发布时对其进行比较。
  4. Token应该被称为'sentinel'或'canary value'。
  5. 为什么Token是size_t?为什么不只是一个空白*?
  6. 你在发布版中检查null应该会抛出一个异常 - 如果代码删除了一个空指针,这不会是一个错误吗?
  7. 您的'correct_token'和'wrong_token'值非常相似。我必须重新阅读代码才能确定。
  8. 鉴于第(3)点,您可以将额外分配的数量加倍,并在前哨/后卫块之前和之后使用。这将检测内存不足和溢出。

答案 2 :(得分:2)

解释为什么选择“ALIGNMENT”作为标识符。解释你选择16的原因。争论你的算法如何捕获最常见的错误,例如溢出堆分配块的末尾或忘记释放内存。

答案 3 :(得分:1)

void* raw = static_cast<char*>(payload) - ALIGNMENT;

如果payload已被删除,是否会导致此未定义的行为?

答案 4 :(得分:1)

我没有非常好地使用硬编码常量/常量字符串 - 将它们放在枚举中?而且我真的没有很好地理解令牌。需要更多评论