Критика моего отладчика кучи
Я написал следующий отладчик кучи, чтобы продемонстрировать утечки памяти, двойные удаления и неправильные формы удалений (т.е. попытаться удалить массив с 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);
}
Ответы
Ответ 1
Вместо того, чтобы выполнять навязчивое замещение, вы можете сохранить список всех распределений. Затем вы можете освободить память, не разрушая ваши собственные данные, и следить за тем, сколько раз определенный адрес "удаляется", а также найти места, где программа пытается удалить несоответствующий адрес (т.е. Не входит в список).
Ответ 2
Это действительно отличное начало. Вот мои 2 цента, поскольку вы просили оставить отзыв:
- Код записывает информацию о трассировке в cerr, что действительно связано с ошибками. Используйте cout для информационных журналов.
- Сумма ALIGNMENT является произвольной. Если код попытался выделить 4090 байт, вы выделили бы 4106, который выливается в следующий блок 4k, который является размером страницы памяти. Вычисленное значение выравнивания было бы лучше... или переименовать ALIGNMENT как HEADER_SIZE или что-то подобное.
- Учитывая заголовок, который вы создаете, вы можете сохранить размер и флаг для "вида памяти" во время распределения и сравнить его со временем выпуска.
- Токен, вероятно, следует называть "дозорным" или "канарейским значением".
- Почему Token - size_t? Почему не просто пустота??
- Ваша проверка на null в выпуске должна, скорее всего, выдать исключение - не было бы ошибкой, если бы код удалил нулевой указатель?
- Значения "correct_token" и "wrong_token" слишком похожи. Я должен был перечитать код, чтобы быть уверенным.
- Учитывая точку (3), вы можете удвоить сумму, которую вы дополнительно выделите, и иметь до и после блоков стражей/защиты. Это позволит обнаружить недостижение и переполнение памяти.
Ответ 3
Объясните, почему вы выбрали "ALIGNMENT" в качестве идентификатора. Объясните, почему вы выбрали 16. Подумайте, как ваш алгоритм ловит наиболее распространенные ошибки, например, переполняет конец блока, выделенного кучей, или забывает освободить память.
Ответ 4
void* raw = static_cast<char*>(payload) - ALIGNMENT;
Если payload
уже удален, не будет ли это поведение undefined?
Ответ 5
Я не очень хорошо использую жестко закодированные константы/константные строки - помещаем их в enum? И я действительно не очень хорошо понимаю идею токена. Требуется больше комментариев