我正在开发像内存泄漏检测器这样的工具。我可以跟踪新的展示位置,但我如何跟踪展示位置删除。我做了很多R& D和我发现放置删除不能直接调用,它在异常时由构造函数调用。那么我如何才能跟踪展示位置删除对新展示位置的影响呢?
任何帮助都将受到赞赏......
答案 0 :(得分:10)
您想要分配和解除分配:
但你对新安置的内容是什么? (明确地说:取一个空格*并且通常简称为“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);
}
}