保持展示位置删除跟踪新位置

时间:2010-10-27 06:36:46

标签: c++

我正在开发像内存泄漏检测器这样的工具。我可以跟踪新的展示位置,但我如何跟踪展示位置删除。我做了很多R& D和我发现放置删除不能直接调用,它在异常时由构造函数调用。那么我如何才能跟踪展示位置删除对新展示位置的影响呢?

任何帮助都将受到赞赏......

6 个答案:

答案 0 :(得分:10)

您想要分配和解除分配:

  • malloc / free
  • new / delete(“常规”表格)
  • new [] / delete []

但你对新安置的内容是什么? (明确地说:取一个空格*并且通常简称为“placement new”的那个,而不是新的其他放置形式。)它是不是删除,而是一个显式的析构函数调用。

  • T *p = new(mem) T(); / p->~T()

Placement new实际上并没有分配任何东西,它只是调用构造函数的语法糖。您不需要也不应该跟踪它。它甚至比其他形式更怪异,因为首先调用“destroy”位然后用另一个替换被破坏的对象(与其他形式的序列相反)并不罕见:

{
  T some_object;

  some_object->~T(); // die! die! die!
  new(&some_object) T();  // didn't save the return value? memory leak..? nope.
} // some_object leaves scope and is destructed (again)

答案 1 :(得分:1)

你没有,没有什么可以追踪的。

Placement new意味着对象放在内存位置。它的存储已经分配。您不会在对象本身上调用任何形式的删除,因为这是您从中获取存储的工作。

就是这样:

void* memory = operator new(sizeof(int)); // A
new (memory) int; // B
operator delete(memory); // C

A和C是您应该跟踪的,而不是B。

答案 2 :(得分:1)

1)使用new new for new allocs

2)使用一个包装函数(例如)delete_object来调用你的分配器,一个用于delete_array。请注意,指针的对齐方式可能会偏离您返回的实际分配(对于数组)。

答案 3 :(得分:1)

首先,不是OP而是其他读者:据我所知,OP是而不是谈论在预分配存储中构建对象。

我不是在谈论这个。

致OP:您没有,只需将operator delete的展示位置转发给普通operator delete即可。毕竟这是将为任何成功构造的动态分配对象调用的释放函数。无论如何你都需要支持它。

如果要将调试信息与分配的内存关联以便在释放函数中使用,那么一个实际的选择是分配比请求更多的位,并将信息放在该块的开头或结尾,返回指向未使用的指针一部分。然后operator delete需要做相反的事情。警告:对齐。

我猜一个不切实际的选择是使用静态std::map(或其他关联数组,如哈希表)。它运行在线程安全问题等,但它避免了对齐问题。


附录,一个完整的例子:

// Note: while this example works nicely, it doesn't support threading.
#include <iostream>
#include <new>          // std::bad_alloc
#include <stddef.h>     // ptrdiff_t, size_t, max_align_t
#include <stdlib.h>     // malloc, EXIT_*,

typedef unsigned char Byte;

struct Source_reference
{
    char const* filename;
    int         line_number;
};

#define SOURCE_REF  Source_reference{ __FILE__, __LINE__ }

auto operator<<( std::ostream& stream, Source_reference const& ref )
    -> std::ostream&
{
    if( ref.filename == nullptr )
    {
        return stream << "[unknown source location]";
    }
    return stream << "\"" << ref.filename << "\"@" << ref.line_number;
}

struct Block_info
{
    Source_reference    source_ref;
    Block_info*         p_prev;
    Block_info*         p_next;

    void link_in_after( Block_info& predecessor )
    {
        p_prev = &predecessor;
        p_next = predecessor.p_next;
        predecessor.p_next = this;
        p_next->p_prev = this;
    }

    void unlink()
    {
        p_next->p_prev = p_prev;
        p_prev->p_next = p_next;
    }
};

namespace g {
    size_t const    max_align   = sizeof( max_align_t );
    size_t const    prefix_size =
        ((sizeof( Block_info ) + max_align - 1)/max_align)*max_align;
    Block_info      block_list_header   =
        {{nullptr,0}, &block_list_header, &block_list_header};
}  // namespace g

auto tracking_alloc( size_t const n_bytes_requested )
    -> void*
{
    size_t const n_bytes = n_bytes_requested + g::prefix_size;
    Byte* const result = static_cast<Byte*>( malloc( n_bytes ) );
    if( !result ) { throw std::bad_alloc(); }

    Block_info* const p_info = ::new( result ) Block_info();
    p_info->link_in_after( g::block_list_header );

    return result + g::prefix_size;
}

void tracking_dealloc( void* p )
{
    Block_info* p_info  = reinterpret_cast<Block_info*>(
        static_cast<Byte*>( p ) - g::prefix_size
        );
    p_info->unlink();
    free( p_info );
}

auto operator new( size_t const n_bytes )
    -> void*
{ return tracking_alloc( n_bytes ); }

auto operator new[]( size_t const n_bytes )
    -> void*
{ return operator new( n_bytes ); }

void operator delete( void* p )
{ tracking_dealloc( p ); }

void operator delete[]( void* p )
{ operator delete( p ); }

auto operator new( size_t const n_bytes, Source_reference const& ref )
    -> void*
{
    Byte* const p               = static_cast<Byte*>( operator new( n_bytes ) );

    Block_info* const p_info    = reinterpret_cast<Block_info*>( p - g::prefix_size );
    p_info->source_ref = ref;

    return p;
}

void operator delete( void* p, Source_reference const& )
{
    using namespace std;
    cout << "!placement delete called." << endl;
    operator delete( p );
}

void list_blocks()
{
    using namespace std;
    cout << "Known allocated blocks:" << endl;
    for(
        Block_info* p_info = g::block_list_header.p_next;
        p_info != &g::block_list_header;
        p_info = p_info->p_next
        )
    {
        void* const p_data = reinterpret_cast<Byte*>( p_info ) + g::prefix_size;
        cout
            << "- Basic allocation " << p_data
            << " from " << p_info->source_ref << "."
            << endl;
    }
    cout << "- End list." << endl;
}

#include <vector>
auto main()
    -> int
{
    using namespace std;

    int* p = new( SOURCE_REF ) int( 42 );
    cout << "An int allocated with ref at " << p << "." << endl;
    list_blocks();

    int* p2 = new int( 43 );
    cout << "\nAn int allocated sans ref at " << p << "." << endl;
    list_blocks();

    {
        vector<double> v( 3 );
        cout << "\nA vector constructed" << endl;
        list_blocks();

        try
        {
            struct Ungood{ Ungood() { throw 666; } };
            cout << "\nAllocating with ref an object that fails construction." << endl;
            new( SOURCE_REF ) Ungood;
        }
        catch( ... )
        {}
        list_blocks();

        delete p;
        cout << "\nThe int-with-ref deleted." << endl;
        list_blocks();
    }
    cout << "\nThe vector destroyed" << endl;
    list_blocks();

    delete p2;
    cout << "\nThe int-sans-ref deleted." << endl;
    list_blocks();
}

使用MinGW g ++ 4.8.2输出:

An int allocated with ref at 0x213c0.
Known allocated blocks:
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

An int allocated sans ref at 0x213c0.
Known allocated blocks:
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

A vector constructed
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

Allocating with ref an object that fails construction.
!placement delete called.
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- Basic allocation 0x213c0 from "foo.cpp"@134.
- End list.

The int-with-ref deleted.
Known allocated blocks:
- Basic allocation 0x21460 from [unknown source location].
- Basic allocation 0x21410 from [unknown source location].
- End list.

The vector destroyed
Known allocated blocks:
- Basic allocation 0x21410 from [unknown source location].
- End list.

The int-sans-ref deleted.
Known allocated blocks:
- End list.

答案 4 :(得分:1)

我对你的一个例子有评论:

MyClass(char * c)
{
  a = new char[10];
  strcpy(a, c);
  throw here ...
}
~MyClass()
{
  delete[] a;
}

我认为您的问题在于在构造函数中使用new而不将其包装在某种资源管理器中。如果构造函数抛出,无论对象是如何在其中新建的(new或placement new),分配给它的内存都将泄漏,除非它由另一个对象管理。

struct Janitor {
    Janitor(char* item) : mgr(item) {}
    ~Janitor() { if uncaught_exception() delete [] mgr; }
    char *mgr;
};

MyClass(char * c)
{
   Janitor j(new char[10]);
        // j is destroyed both if the rest of the contstructor succeeds
        // and if it throws

   //unsafe code
   strcpy(j.mgr, c);
   ...

   //at the end
   //pass the resource management to MyClass
   a = j.mgr;
};

其中一些也可能有用 http://www.gotw.ca/gotw/008.htm
http://www.gotw.ca/gotw/056.htm
http://www.gotw.ca/gotw/010.htm
http://www.gotw.ca/gotw/022.htm
http://www.gotw.ca/gotw/042.htm

答案 5 :(得分:0)

在基类的接口中声明operator new/new[]/delete/delete[],(以及内置变体或隐藏它们)。然后实施你需要的那些。

这应该可用于某些目的,如果您只想跟踪特定类层次结构(或集合)中对象的泄漏。

插图(注意,伪代码如下):

/* option 1) use a specified allocator for all class instances */
namespace { const char* const TypeID("MON::t_object"); }

void* MON::t_object::operator new(size_t size) {
    void* const allocation(AllocatorForType(TypeID).operator_new(size));
    std::cout << "MON::t_object::operator new - size: " << size << " TypeID: " << TypeID << " address: " << allocation << "\n";
    return allocation;
}

/* option 2) use a specific allocator via placement, or type overload */
void* MON::t_object::operator new(size_t size, t_allocator& allocator) {
    void* const allocation(allocator.operator_new(size));
    std::cout << "MON::t_object::operator new - size: " << size << " allocator: " << & allocator << " address: " << allocation << "\n";
    return allocation;
}

void MON::t_object::operator delete(void* allocation) {

    std::cout << "MON::t_object::operator delete: " << allocation << "\n";

    /* now call through your allocator interface */
    if ( /* using option 1 */ ) {
        AllocatorForType(TypeID).operator_delete(allocation);
    }
    else /* using option 2 */ {
        AllocatorForAllocationWithAddress(allocation).operator_delete(allocation);
    }
}