【问题标题】:Critique my heap debugger批评我的堆调试器
【发布时间】:2011-02-19 06:59:55
【问题描述】:

我编写了以下堆调试器,以向初学者演示内存泄漏、双重删除和错误形式的删除(即尝试使用delete p 而不是delete[] 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);
}

【问题讨论】:

    标签: c++ debugging heap-memory


    【解决方案1】:

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

    【讨论】:

    • 我如何确保笔记列表不跟踪自己的内存?写一个分配器?自己写基于malloc和free的list实现?
    • 我的第一个非侵入式堆调试器的实现可以在here找到。
    【解决方案2】:

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

    【讨论】:

    • 您的意思是“向您解释”还是“将 cmets 添加到代码中”?我选择了“对齐”,因为任意对象的内存分配必须按适用于每种类型的某个数字对齐。我选择 16 是因为我不知道确定实际值的可移植方式,所以欢迎提出更好的建议 :) 我的堆调试器根本没有涵盖缓冲区溢出。通过检查一个计数器来覆盖忘记释放内存,该计数器随着每次执行new 而增加并随着每次执行delete 而减少,请参阅~heap_debugger 中的代码。
    【解决方案3】:

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

    1. 代码将跟踪信息写入cerr,这确实是为了错误。使用 cout 获取信息日志。
    2. 对齐量是任意的。如果代码尝试分配 4090 字节,您将分配 4106,它会溢出到下一个 4k 块,即内存页的大小。计算出的对齐值会更好...或将 ALIGNMENT 重命名为 HEADER_SIZE 或类似名称。
    3. 鉴于您正在创建的标头,您可以在分配时存储“内存类型”的大小和标志,并在释放时进行比较。
    4. 令牌可能应该被称为“哨兵”或“金丝雀价值”。
    5. 为什么 Token 是 size_t?为什么不只是一个 void * ?
    6. 您在 release 中对 null 的检查可能会引发异常 - 如果代码删除了一个空指针,这不是错误吗?
    7. 您的 'correct_token' 和 'wrong_token' 值太相似了。我必须重新阅读代码才能确定。
    8. 鉴于第 (3) 点,您可以将额外分配的数量翻倍,并在哨兵/守卫块之前和之后拥有。这将检测内存不足和溢出。

    【讨论】:

    • 2.如何计算适用于每种类型的值? | 5. 不明白为什么固定的整数值应该是void*,请解释一下。 | 6. 不,delete 0 必须是 noop。抛出异常将违反删除合同。 | 7. 是的,感谢您的观察。 | 8. 虽然不是我最初的目标,但这可能仍然是一个好主意。谢谢!
    • 1.或者clog?我认为重点是能够重定向日志输出。
    • @FredOverflow: (2) 基于 sizeof(type) 的东西,或者只是做一些数学运算来计算出一个合理的对齐值,沿着 size += (ALIGMENT - (size % ALIGNMENT) ) + (HEADER_SIZE * 2)? (5) 除了传统,我想不出任何理由:-|
    • 如何将元数据放在块的末尾(对齐到 4 或 8 字节边界)?
    【解决方案4】:

    我没有很好地关注您对硬编码常量/常量字符串的使用 - 将它们放在枚举中?而且我真的不太了解令牌的想法。需要更多的cmets

    【讨论】:

    • 啊,你的意思是令牌和“数组/非数组”的东西。是的,这是有道理的。谢谢。
    【解决方案5】:
    void* raw = static_cast<char*>(payload) - ALIGNMENT;
    

    如果payload 已经被删除,那不会造成这种未定义的行为吗?

    【讨论】:

    • 说实话,我不知道。我希望在过去某个时间点new 交回一个指针时,再次使用它不会导致太多的恶魔从我的鼻子里飞出来。但我不确定。
    • 我的意思是,如果你完全依赖指针是有效的开头,我看不到你如何希望检查你是否得到了一个有效的指针(否则鼻恶魔 会 飞)。我想您可能需要重新考虑 bitc 的回答...
    猜你喜欢
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-03
    • 2010-12-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多