C ++
这是昨天批评我的堆调试器的后续行动。 正如bitc所建议的,我现在将关于分配块的元数据保存在单独的手写散列表中。
堆调试器现在检测到以下类型的错误:
欢迎提前讨论并提前致谢!
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <new>
namespace
{
// I don't want to #include <algorithm> for a single function template :)
template <typename T>
void my_swap(T& x, T& y)
{
T z(x);
x = y;
y = z;
}
typedef unsigned char byte;
const byte CANARY[] = {0x5A, 0xFE, 0x6A, 0x8D,
0x5A, 0xFE, 0x6A, 0x8D,
0x5A, 0xFE, 0x6A, 0x8D,
0x5A, 0xFE, 0x6A, 0x8D};
bool canary_dead(const byte* cage)
{
bool dead = memcmp(cage, CANARY, sizeof CANARY);
if (dead)
{
for (size_t i = 0; i < sizeof CANARY; ++i)
{
byte b = cage[i];
printf(b == CANARY[i] ? "__ " : "%2X ", b);
}
putchar('n');
}
return dead;
}
enum kind_of_memory {AVAILABLE, TOMBSTONE, NON_ARRAY_MEMORY, ARRAY_MEMORY};
const char* kind_string[] = {0, 0, "non-array memory", " array memory"};
struct metadata
{
byte* address;
size_t size;
kind_of_memory kind;
bool in_use() const
{
return kind & 2;
}
void print() const
{
printf("%s at %p (%d bytes)n", kind_string[kind], address, size);
}
bool must_keep_searching_for(void* address)
{
return kind == TOMBSTONE || (in_use() && address != this->address);
}
bool canaries_alive() const
{
bool alive = true;
if (canary_dead(address - sizeof CANARY))
{
printf("ERROR: buffer underflow at %pn", address);
alive = false;
}
if (canary_dead(address + size))
{
printf("ERROR: buffer overflow at %pn", address);
alive = false;
}
return alive;
}
};
const size_t MINIMUM_CAPACITY = 11;
class hashtable
{
metadata* data;
size_t used;
size_t capacity;
size_t tombstones;
public:
size_t size() const
{
return used - tombstones;
}
void print() const
{
for (size_t i = 0; i < capacity; ++i)
{
if (data[i].in_use())
{
printf(":( leaked ");
data[i].print();
}
}
}
hashtable()
{
used = 0;
capacity = MINIMUM_CAPACITY;
data = static_cast<metadata*>(calloc(capacity, sizeof(metadata)));
tombstones = 0;
}
~hashtable()
{
free(data);
}
hashtable(const hashtable& that)
{
used = 0;
capacity = 3 * that.size() | 1;
if (capacity < MINIMUM_CAPACITY) capacity = MINIMUM_CAPACITY;
data = static_cast<metadata*>(calloc(capacity, sizeof(metadata)));
tombstones = 0;
for (size_t i = 0; i < that.capacity; ++i)
{
if (that.data[i].in_use())
{
insert_unsafe(that.data[i]);
}
}
}
hashtable& operator=(hashtable copy)
{
swap(copy);
return *this;
}
void swap(hashtable& that)
{
my_swap(data, that.data);
my_swap(used, that.used);
my_swap(capacity, that.capacity);
my_swap(tombstones, that.tombstones);
}
void insert_unsafe(const metadata& x)
{
*find(x.address) = x;
++used;
}
void insert(const metadata& x)
{
if (2 * used >= capacity)
{
hashtable copy(*this);
swap(copy);
}
insert_unsafe(x);
}
metadata* find(void* address)
{
size_t index = reinterpret_cast<size_t>(address) % capacity;
while (data[index].must_keep_searching_for(address))
{
++index;
if (index == capacity) index = 0;
}
return &data[index];
}
void erase(metadata* it)
{
it->kind = TOMBSTONE;
++tombstones;
}
} the_hashset;
struct heap_debugger
{
heap_debugger()
{
puts("heap debugger started");
}
~heap_debugger()
{
the_hashset.print();
puts("heap debugger shutting down");
}
} the_heap_debugger;
void* allocate(size_t size, kind_of_memory kind) throw (std::bad_alloc)
{
byte* raw = static_cast<byte*>(malloc(size + 2 * sizeof CANARY));
if (raw == 0) throw std::bad_alloc();
memcpy(raw, CANARY, sizeof CANARY);
byte* payload = raw + sizeof CANARY;
memcpy(payload + size, CANARY, sizeof CANARY);
metadata md = {payload, size, kind};
the_hashset.insert(md);
printf("allocated ");
md.print();
return payload;
}
void release(void* payload, kind_of_memory kind) throw ()
{
if (payload == 0) return;
metadata* p = the_hashset.find(payload);
if (!p->in_use())
{
printf("ERROR: no dynamic memory at %pn", payload);
}
else if (p->kind != kind)
{
printf("ERROR:wrong form of delete at %pn", payload);
}
else if (p->canaries_alive())
{
printf("releasing ");
p->print();
free(static_cast<byte*>(payload) - sizeof CANARY);
the_hashset.erase(p);
}
}
}
void* operator new(size_t size) throw (std::bad_alloc)
{
return allocate(size, NON_ARRAY_MEMORY);
}
void* operator new[](size_t size) throw (std::bad_alloc)
{
return allocate(size, ARRAY_MEMORY);
}
void operator delete(void* payload) throw ()
{
release(payload, NON_ARRAY_MEMORY);
}
void operator delete[](void* payload) throw ()
{
release(payload, ARRAY_MEMORY);
}
int main()
{
int* p = new int[1];
delete p; // wrong form of delete
delete[] p; // ok
delete p; // no dynamic memory (double delete)
p = new int[1];
p[-1] = 0xcafebabe;
p[+1] = 0x12345678;
delete[] p; // underflow and overflow prevent release
// p is not released, hence leak
}
的确很好。 你的金丝雀实际上可以揭示一些真正的上溢/下溢情况(尽管并非Matthieu指出的那样)。
还有什么。 您可能会遇到一些多线程应用程序的问题。 也许保护哈希表的并发访问?
现在您记录了每个分配和释放,您可以(如果您喜欢)提供有关正在测试的程序的更多信息。 知道在任何特定时间分配的总数和平均数可能很有趣? 分配的总,最大,最小和平均字节数以及分配的平均寿命。
如果你想比较不同的线程,至少用Pthreads你可以用pthread_self()来标识它们。 这个堆调试器可能会成为一个非常有用的分析工具。
你是否正在使用一个非常微弱的malloc,它还没有内置这种东西? 因为如果它在那里,你就会把收益增加一倍。 而且,这种系统在进行小对象分配时确实很痛苦,或者因为人们自己分配和管理内存而对他们无效。
就代码而言,它看起来会按照你所说的做,它看起来设计得很好,而且易于阅读。 但是,如果您要经历这样做的麻烦,为什么不使用托管容器/指针/运算符[]来捕获缓冲区在源代码上/下的流动。 这样,您就可以在发生故障时进行调试,而不是免费发现发生了某种恶事。
我相信其他人会找到效率很高的东西,但这些仅仅是几分钟后查看你的代码后的一些想法。
我想知道下溢/溢出的检测。
我的意思是,如果我有10个元素数组,那么看起来你会发现我是否在-1
和10
处写,但如果我在20
写? 下溢或溢出不一定是缓冲区溢出(连续)的一部分。
此外,阻止块的发布有什么意义? 这块(相对)很好,它是你的邻居(不幸)被损坏了。
无论如何,这对我来说似乎相当不错,尽管我可能每个函数都有不止一个返回值,因为Single Exit没有任何意义。 你看起来更像一个C程序员而不是C ++ :)
链接地址: http://www.djcxy.com/p/12687.html上一篇: c++
下一篇: How should I write ISO C++ Standard conformant custom new and delete operators?