常用的IDE如Visual Studio在调试模式下之所以能通过查看堆栈等来追踪变量、调试,是因为IDE在申请内存的时候在实际返回给用户的内存的前后多申请了部分内存用于存储调试信息。
此处做个模拟调试器,草稿版本是对原作者实现的复现;改进版是加入了对变量名的追踪,更容易看到所申请内存是哪个变量。
1.草稿版本
test_tracking_buffer.hpp
#include <iostream>
#include <string>
#include <vector>
// !注意,SrcRef成员的构造函数不能使用new或std::allocator,亦即
// SrcRef不能包含构造函数使用new或std::allocator的对象
// 否则会使operator new产生无限循环递归调用
typedef struct SourceReference
{
//std::string file_; // 灾难,会让后文重载的operator new产生无限递归调用自身,无法退出
const char* file_;
int line_;
}SrcRef;
auto operator<<(std::ostream& os, const SrcRef& src_ref) -> std::ostream&
{
if (src_ref.file_ != nullptr/*""*/)
{
os << "\"" << src_ref.file_ << "\"@" << src_ref.line_;
}
else
{
os << "[unknown source location]";
}
return os;
}
#define SRC_REF SrcRef{__FILE__, __LINE__}
struct BlockInfo
{
SrcRef src_ref_;
BlockInfo *pre_;
BlockInfo *next_;
void link_after(BlockInfo &head) {
pre_ = &head;
next_ = head.next_;
head.next_ = this;
next_->pre_ = this;
}
void unlink() {
pre_->next_ = next_;
next_->pre_ = pre_;
}
};
namespace g
{
std::size_t max_align_size = sizeof(max_align_t);
std::size_t extra_prefix_size = (sizeof(BlockInfo) + max_align_size - 1) / max_align_size * max_align_size;
// BlockInfo gblock_head{ {nullptr,0},nullptr, nullptr }; // 链表头gblock_head的pre_、next_应初始化指向gblock_head
// BlockInfo gblock_head{ {"",0},&gblock_head, &gblock_head };
BlockInfo gblock_head{ {nullptr,0},&gblock_head, &gblock_head };
}
auto track_alloc(std::size_t num_bytes) ->void*
{
std::size_t total_bytes = num_bytes + g::extra_prefix_size;
void* total_buf = malloc(total_bytes);
if (NULL == total_bytes)
{
throw std::bad_alloc();
}
BlockInfo* block_info = new(total_buf) BlockInfo();
block_info->link_after(g::gblock_head);
return total_buf;
}
auto track_dealloc(void*p)->void
{
BlockInfo* block_info = (BlockInfo *)((unsigned char*)p - g::extra_prefix_size);
block_info->unlink();
free(block_info);
}
auto operator new(std::size_t num_bytes) ->void*
{
void* total_buf = track_alloc(num_bytes);
return (unsigned char*)total_buf + g::extra_prefix_size;
}
auto operator new[](std::size_t num_bytes) ->void*
{
return operator new(num_bytes);
}
auto operator delete(void* p)->void
{
track_dealloc(p);
}
auto operator delete[](void* p)->void
{
operator delete(p);
}
auto operator new(std::size_t num_bytes, const SrcRef& src_ref) ->void*
{
void* total_buf = track_alloc(num_bytes);
BlockInfo* block_info = (BlockInfo*)total_buf;
block_info->src_ref_.file_ = src_ref.file_;
block_info->src_ref_.line_ = src_ref.line_;
return (unsigned char*)total_buf + g::extra_prefix_size;
}
auto operator new[](std::size_t num_bytes, const SrcRef& src_ref) ->void*
{
return operator new(num_bytes, src_ref);
}
auto operator delete(void* p, const SrcRef& src_ref)->void
{
std::cout << "placement delete Only called when ctor throws an exception! " << std::endl;
operator delete(p);
}
auto operator delete[](void* p, const SrcRef& src_ref)->void
{
operator delete(p, src_ref);
}
void list_blocks()
{
std::cout << "List begin: " << std::endl;
for (BlockInfo* it = g::gblock_head.next_; it != &g::gblock_head; it = it->next_)
{
std::cout << " - Basic allocation " << it + g::extra_prefix_size << " from " << it->src_ref_ << std::endl;
}
std::cout << "List end.\n" << std::endl;
}
auto test_main() ->void
{
int *pa = new(SRC_REF)int(42);
list_blocks();
int *pb = new int(32);
list_blocks();
{
std::vector<double> vec(3);
list_blocks();
try
{
struct Ungood { Ungood() { throw 444; } };
new(SRC_REF)Ungood();
}
catch (...)
{
}
list_blocks();
delete pa;
list_blocks();
}
list_blocks();
delete pb;
list_blocks();
}
main.cpp
#include "test_tracking_buffer.hpp"
auto main()->int
{
test_main();
return 0;
}
输出:
List begin:
- Basic allocation 0000000000449450 from "d:\projects\test_tracking_buffer.hpp"@145
List end.
List begin:
- Basic allocation 00000000004494B0 from [unknown source location]
- Basic allocation 0000000000449450 from "d:\projects\test_tracking_buffer.hpp"@145
List end.
List begin:
- Basic allocation 000000000044D810 from [unknown source location]
- Basic allocation 000000000044BE30 from [unknown source location]
- Basic allocation 00000000004494B0 from [unknown source location]
- Basic allocation 0000000000449450 from "d:\projects\test_tracking_buffer.hpp"@145
List end.
placement delete Only called when ctor throws an exception!
List begin:
- Basic allocation 000000000044D810 from [unknown source location]
- Basic allocation 000000000044BE30 from [unknown source location]
- Basic allocation 00000000004494B0 from [unknown source location]
- Basic allocation 0000000000449450 from "d:\projects\test_tracking_buffer.hpp"@145
List end.
List begin:
- Basic allocation 000000000044D810 from [unknown source location]
- Basic allocation 000000000044BE30 from [unknown source location]
- Basic allocation 00000000004494B0 from [unknown source location]
List end.
List begin:
- Basic allocation 00000000004494B0 from [unknown source location]
List end.
List begin:
List end.
2.改进版本
test_tracking_buffer.hpp
#include <iostream>
#include <string>
#include <vector>
// !注意,SrcRef成员的构造函数不能使用new或std::allocator,亦即
// SrcRef不能包含构造函数使用new或std::allocator的对象
// 否则会使operator new产生无限循环递归调用
typedef struct SourceReference
{
//std::string file_; // 灾难,会让后文重载的operator new产生无限递归调用自身,无法退出
const char* file_;
int line_;
const char* var_name_{ "ANONYMITY" };
}SrcRef;
#define SRC_REF(var) SrcRef{__FILE__, __LINE__, #var}
struct BlockInfo
{
SrcRef src_ref_;
BlockInfo *pre_;
BlockInfo *next_;
void link_after(BlockInfo &head) {
pre_ = &head;
next_ = head.next_;
head.next_ = this;
next_->pre_ = this;
}
void unlink() {
pre_->next_ = next_;
next_->pre_ = pre_;
}
};
namespace g
{
std::size_t max_align_size = sizeof(max_align_t);
std::size_t extra_prefix_size = (sizeof(BlockInfo) + max_align_size - 1) / max_align_size * max_align_size;
BlockInfo gblock_head{ {nullptr,0},&gblock_head, &gblock_head };
}
auto track_alloc(std::size_t num_bytes) ->void*
{
std::size_t total_bytes = num_bytes + g::extra_prefix_size;
void* total_buf = malloc(total_bytes);
if (NULL == total_bytes)
{
throw std::bad_alloc();
}
BlockInfo* block_info = new(total_buf) BlockInfo();
block_info->link_after(g::gblock_head);
return total_buf;
}
auto track_dealloc(void*p)->void
{
BlockInfo* block_info = (BlockInfo *)((unsigned char*)p - g::extra_prefix_size);
block_info->unlink();
free(block_info);
}
auto operator new(std::size_t num_bytes) ->void*
{
void* total_buf = track_alloc(num_bytes);
return (unsigned char*)total_buf + g::extra_prefix_size;
}
auto operator new[](std::size_t num_bytes) ->void*
{
return operator new(num_bytes);
}
auto operator delete(void* p)->void
{
track_dealloc(p);
}
auto operator delete[](void* p)->void
{
operator delete(p);
}
auto operator new(std::size_t num_bytes, const SrcRef& src_ref) ->void*
{
void* total_buf = track_alloc(num_bytes);
BlockInfo* block_info = (BlockInfo*)total_buf;
block_info->src_ref_ = src_ref; // 逐成员赋值更好
return (unsigned char*)total_buf + g::extra_prefix_size;
}
auto operator new[](std::size_t num_bytes, const SrcRef& src_ref) ->void*
{
return operator new(num_bytes, src_ref);
}
auto operator delete(void* p, const SrcRef& src_ref)->void
{
std::cout << "placement delete Only called when ctor throws an exception! " << std::endl;
operator delete(p);
}
auto operator delete[](void* p, const SrcRef& src_ref)->void
{
operator delete(p, src_ref);
}
void list_blocks()
{
std::cout << "Memory list begin: " << std::endl;
for (BlockInfo* it = g::gblock_head.next_; it != &g::gblock_head; it = it->next_)
{
std::cout << it->src_ref_.var_name_ << "=" << it + g::extra_prefix_size << ", allocated from \"";
if (it->src_ref_.file_)
{
std::cout << it->src_ref_.file_ << "\"@" << it->src_ref_.line_ << std::endl;
}
else
{
std::cout << "[unknown source location]" << std::endl;
}
}
std::cout << "Memory list end.\n" << std::endl;
}
auto test_main() ->void
{
int *pa = new(SRC_REF(pa))int(42);
std::cout << "-->pa:" << std::endl;
list_blocks();
int *pb = new int(32);
std::cout << "-->pb:" << std::endl;
list_blocks();
{
struct NotTooBad
{
NotTooBad()
{
try
{
throw 444;
}
catch (...)
{
std::cout << "ctor throws an exception." << std::endl;
}
}
};
NotTooBad*pnot_too_bad = new(SRC_REF(pnot_too_bad)) NotTooBad(); // 内存申请成功,list中会有p_not_too_bad的信息
std::cout << "-->pnot_too_bad:" << std::endl;
list_blocks();
delete pnot_too_bad;
list_blocks();
}
{
std::vector<double> vec(3); // 此处会申请两次auto operator new(std::size_t num_bytes) ->void*,#TODO,原因未知
std::cout << "-->vec(3):" << std::endl;
list_blocks();
try
{
struct Ungood { Ungood() { throw 444; } };
new(SRC_REF(rightval))Ungood(); // 内存申请失败,list中不会有rightval的信息
}
catch (...)
{
}
list_blocks();
delete pa;
list_blocks();
}
list_blocks();
delete pb;
list_blocks();
}
main.cpp
#include "test_tracking_buffer.hpp"
auto main()->int
{
test_main();
return 0;
}
输出:
-->pa:
Memory list begin:
pa=00000000002AC000, allocated from "d:\projects\test_tracking_buffer.hpp"@317
Memory list end.
-->pb:
Memory list begin:
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
pa=00000000002AC000, allocated from "d:\projects\test_tracking_buffer.hpp"@317
Memory list end.
ctor throws an exception.
-->pnot_too_bad:
Memory list begin:
pnot_too_bad=00000000002ADAF0, allocated from "d:\projects\test_tracking_buffer.hpp"@340
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
pa=00000000002AC000, allocated from "d:\projects\test_tracking_buffer.hpp"@317
Memory list end.
Memory list begin:
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
pa=00000000002AC000, allocated from "d:\projects\test_tracking_buffer.hpp"@317
Memory list end.
-->vec(3):
Memory list begin:
ANONYMITY=00000000002AEB40, allocated from "[unknown source location]
ANONYMITY=00000000002AEAC0, allocated from "[unknown source location]
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
pa=00000000002AC000, allocated from "d:\projects\test_tracking_buffer.hpp"@317
Memory list end.
placement delete Only called when ctor throws an exception!
Memory list begin:
ANONYMITY=00000000002AEB40, allocated from "[unknown source location]
ANONYMITY=00000000002AEAC0, allocated from "[unknown source location]
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
pa=00000000002AC000, allocated from "d:\projects\test_tracking_buffer.hpp"@317
Memory list end.
Memory list begin:
ANONYMITY=00000000002AEB40, allocated from "[unknown source location]
ANONYMITY=00000000002AEAC0, allocated from "[unknown source location]
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
Memory list end.
Memory list begin:
ANONYMITY=00000000002ADA50, allocated from "[unknown source location]
Memory list end.
Memory list begin:
Memory list end.
Reference
keep the track of placement delete against placement new
参考资料的源码:
// 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();
}
参考资料源码的输出:
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.