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个元素数组,那么看起来你会发现我是否在-110处写,但如果我在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?