如何在 C++ 中跟踪内存分配(尤其是新建/删除)

2021-12-14 00:00:00 debugging memory-management c++

我如何在 C++ 中跟踪内存分配,尤其是那些由 new/delete 完成的.对于对象,我可以轻松覆盖 operator new,但我不确定如何全局覆盖所有分配,以便它们通过我的自定义 new/delete.这应该不是什么大问题,但我不确定应该如何完成(#define new MY_NEW?).

How can I track the memory allocations in C++, especially those done by new/delete. For an object, I can easily override the operator new, but I'm not sure how to globally override all allocations so they go through my custom new/delete. This should be not a big problem, but I'm not sure how this is supposed to be done (#define new MY_NEW?).

只要这可行,我就会假设在分配的指针/位置的某处有一个映射就足够了,这样我就可以跟踪当前活动"的所有分配,并且 - 在应用程序结束时 -检查尚未释放的分配.

As soon as this works, I would assume it's enough to have a map somewhere of pointer/location of the allocation, so I can keep track of all allocations which are currently 'active' and - at the end of the application - check for allocations which have not been freed.

嗯,这似乎又是一件肯定至少已经做过几次的事情,所以有什么好的图书馆(最好是便携式的)?

Well, this seems again like something that surely has been done several times at least, so any good library out there (preferably a portable one)?

推荐答案

我建议你使用 valgrind for linux.它将捕获未释放的内存,以及其他错误,例如写入未分配的内存.另一种选择是mudflap,它也告诉您未释放的内存.在 gcc 中使用 -fmudflap -lmudflap 选项,然后使用 MUDFLAP_OPTIONS=-print-leaks ./my_program 启动你的程序.

I would recommend you to use valgrind for linux. It will catch not freed memory, among other bugs like writing to unallocated memory. Another option is mudflap, which tells you about not freed memory too. Use -fmudflap -lmudflap options with gcc, then start your program with MUDFLAP_OPTIONS=-print-leaks ./my_program.

这是一些非常简单的代码.它不适合复杂的跟踪,但旨在向您展示原则上如何进行,如果您要自己实现它.像这样的东西(省略了调用注册的 new_handler 和其他细节的东西).

Here's some very simple code. It's not suitable for sophisticated tracking, but intended to show you how you would do it in principle, if you were to implement it yourself. Something like this (left out stuff calling the registered new_handler and other details).

template<typename T>
struct track_alloc : std::allocator<T> {
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::size_type size_type;

    template<typename U>
    struct rebind {
        typedef track_alloc<U> other;
    };

    track_alloc() {}

    template<typename U>
    track_alloc(track_alloc<U> const& u)
        :std::allocator<T>(u) {}

    pointer allocate(size_type size, 
                     std::allocator<void>::const_pointer = 0) {
        void * p = std::malloc(size * sizeof(T));
        if(p == 0) {
            throw std::bad_alloc();
        }
        return static_cast<pointer>(p);
    }

    void deallocate(pointer p, size_type) {
        std::free(p);
    }
};

typedef std::map< void*, std::size_t, std::less<void*>, 
                  track_alloc< std::pair<void* const, std::size_t> > > track_type;

struct track_printer {
    track_type * track;
    track_printer(track_type * track):track(track) {}
    ~track_printer() {
        track_type::const_iterator it = track->begin();
        while(it != track->end()) {
            std::cerr << "TRACK: leaked at " << it->first << ", "
                      << it->second << " bytes
";
            ++it;
        }
    }
};

track_type * get_map() {
    // don't use normal new to avoid infinite recursion.
    static track_type * track = new (std::malloc(sizeof *track)) 
        track_type;
    static track_printer printer(track);
    return track;
}

void * operator new(std::size_t size) throw(std::bad_alloc) {
    // we are required to return non-null
    void * mem = std::malloc(size == 0 ? 1 : size);
    if(mem == 0) {
        throw std::bad_alloc();
    }
    (*get_map())[mem] = size;
    return mem;
}

void operator delete(void * mem) throw() {
    if(get_map()->erase(mem) == 0) {
        // this indicates a serious bug
        std::cerr << "bug: memory at " 
                  << mem << " wasn't allocated by us
";
    }
    std::free(mem);
}

int main() {
    std::string *s = new std::string;
        // will print something like: TRACK: leaked at 0x9564008, 4 bytes
}

我们必须为我们的地图使用我们自己的分配器,因为标准分配器将使用我们覆盖的运算符 new,这将导致无限递归.

We have to use our own allocator for our map, because the standard one will use our overridden operator new, which would result in an infinite recursion.

确保如果您覆盖 operator new,您使用映射来注册您的分配.删除由 new 的放置形式分配的内存也将使用该 delete 运算符,因此如果您不知道的某些代码重载了 operator new 而不使用您的映射,这可能会变得棘手,因为运算符 delete 会告诉您它没有分配并且使用 std::free 释放内存.

Make sure if you override operator new, you use the map to register your allocations. Deleting memory allocated by placement forms of new will use that delete operator too, so it can become tricky if some code you don't know has overloaded operator new not using your map, because operator delete will tell you that it wasn't allocated and use std::free to free the memory.

另请注意,正如 Pax 在他的解决方案中指出的那样,这只会显示由使用我们自己定义的运算符 new/delete 的代码引起的泄漏.因此,如果您想使用它们,请将它们的声明放在标题中,并将其包含在所有应注意的文件中.

Also note, as Pax pointed out for his solution too, this will only show leaks that are caused by code using our own defined operator new/delete. So if you want to use them, put their declaration in a header, and include it in all files that should be watched.

相关文章