Критикуйте мой отладчик "кучи"

Я записал следующий отладчик "кучи" для демонстрации утечек памяти, дважды удаляет, и неправильные формы удаляет (т.е. пытающийся удалить массив с 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);
}
5
задан fredoverflow 13 May 2010 в 20:59
поделиться

5 ответов

Вместо того, чтобы вести навязчивые записи, вы можете вести список всех выделенных ресурсов. Затем вы можете освободить память, не уничтожая свои собственные данные, и отслеживать, сколько раз конкретный адрес «удалялся», а также находить места, где программа пытается удалить несовпадающий адрес (то есть не в списке).

4
ответ дан 13 December 2019 в 22:03
поделиться
void* raw = static_cast<char*>(payload) - ALIGNMENT;

Если полезная нагрузка уже удалена, не будет ли это неопределенным поведением?

1
ответ дан 13 December 2019 в 22:03
поделиться

Объясните, почему вы выбрали "ALIGNMENT" в качестве идентификатора. Объясните, почему вы выбрали 16. Аргументируйте, как ваш алгоритм отлавливает наиболее распространенные ошибки, такие как переполнение конца блока, выделенного из кучи, или забывание освободить память.

2
ответ дан 13 December 2019 в 22:03
поделиться

Это действительно отличное начало. Вот мои 2 цента, как вы просили оставить отзыв:

  1. Код записывает информацию трассировки в cerr, что действительно для ошибок. Используйте cout для информационных журналов.
  2. Сумма ВЫРАВНИВАНИЯ произвольна. Если код пытается выделить 4090 байт, вы должны выделить 4106 байт, которые перейдут в следующий блок размером 4 КБ, который является размером страницы памяти. Было бы лучше рассчитать значение выравнивания ... или переименуйте ALIGNMENT в HEADER_SIZE или что-то подобное.
  3. Учитывая создаваемый вами заголовок, вы можете сохранить размер и флаг для «типа памяти» во время выделения и сравнить их во время выпуска.
  4. Токен, вероятно, следует называть «дозорным» или «канарейкой».
  5. Почему у токена size_t? Почему бы не использовать просто void *?
  6. Ваша проверка на null в выпуске, вероятно, должна вызвать исключение - не будет ли это ошибкой, если код удалил нулевой указатель?
  7. Ваши значения 'right_token' и 'invalid_token' слишком похожи. Чтобы убедиться, мне пришлось перечитать код.
  8. Учитывая пункт (3), вы можете удвоить сумму, которую вы дополнительно выделяете, и иметь до и после сторожевых / охранных блоков. Это обнаружит опустошение и переполнение памяти.
2
ответ дан 13 December 2019 в 22:03
поделиться

Я не очень хорошо слежу за тем, как вы используете жестко запрограммированные константы / константные строки - поместить их в перечисление? И я действительно не очень хорошо понимаю токен. Требуется больше комментариев

1
ответ дан 13 December 2019 в 22:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: