重载new/delete运算符时内存跟踪不准的问题及方案咨询
Hey there! Let's dig into your memory tracking problem and figure out how to get that precise tracking you want.
The mismatch between the 4 bytes you requested for an int and the 8 bytes your delete handler reports is almost certainly due to allocator metadata or memory alignment requirements. Most heap allocators add small chunks of extra data to each allocation—things like block size, flags, or pointers to adjacent blocks—to manage the heap efficiently. Additionally, many systems enforce memory alignment (e.g., 8-byte alignment for 64-bit systems), so even small allocations get rounded up to the nearest alignment boundary.
When you call free() in your delete operator, you're seeing the actual total size the allocator reserved, not the size you requested. To fix this, we need to track the user-requested size instead.
Here's a revised approach that tracks exactly the memory you requested, plus adds support for tracking dynamic objects and their sizes:
First, update your global new/delete overloads to record the requested size when allocating, then look up that size when freeing:
#include <iostream> #include <unordered_map> #include <typeinfo> #include <utility> // Static tracking state (thread-safe note: add a mutex if using multi-threaded code!) static std::unordered_map<void*, std::pair<size_t, const char*>> allocated_blocks; static size_t total_requested_allocated = 0; static size_t total_requested_freed = 0; // Overload global new to track requested size and optional type info void* operator new(size_t size, const char* type_name = "unknown") { void* ptr = malloc(size); if (ptr) { allocated_blocks[ptr] = {size, type_name}; total_requested_allocated += size; std::cout << "[ALLOC] " << size << " bytes (" << type_name << ") at " << ptr << "\n"; } return ptr; } // Overload global delete to use the tracked size void operator delete(void* ptr) noexcept { if (!ptr) return; auto it = allocated_blocks.find(ptr); if (it != allocated_blocks.end()) { total_requested_freed += it->second.first; std::cout << "[FREE] " << it->second.first << " bytes (" << it->second.second << ") at " << ptr << "\n"; allocated_blocks.erase(it); } free(ptr); } // Don't forget array allocations! void* operator new[](size_t size, const char* type_name = "array-unknown") { return operator new(size, type_name); } void operator delete[](void* ptr) noexcept { operator delete(ptr); } // Helper template to track object types automatically template<typename T, typename... Args> T* tracked_new(Args&&... args) { return new(typeid(T).name()) T(std::forward<Args>(args)...); }
Key Improvements Here:
- We store the user-requested size in a map (linked to the allocation pointer) instead of relying on the allocator's reported size during deletion.
- Added support for tracking object types using
typeid, so you can see exactly what kind of object was allocated. - Included overloads for array
new[]/delete[]—easy to forget, but critical for complete tracking. - Added a
tracked_newtemplate function to automatically pass type info without manual work.
To track individual objects (not just raw memory), you can extend this setup in a couple ways:
1. Per-Class Tracking
If you want detailed stats for specific classes, add static counters and class-specific new/delete overloads:
class MyCustomObj { private: int data[10]; static size_t live_object_count; public: MyCustomObj() { live_object_count++; } ~MyCustomObj() { live_object_count--; } // Class-specific new to track type automatically void* operator new(size_t size) { return ::operator new(size, typeid(MyCustomObj).name()); } static size_t get_live_count() { return live_object_count; } }; size_t MyCustomObj::live_object_count = 0;
2. Global Object Type Stats
You can modify the tracking map to aggregate stats by type:
#include <unordered_map> static std::unordered_map<const char*, size_t> type_allocation_counts; // Update the new operator to increment type counts void* operator new(size_t size, const char* type_name = "unknown") { void* ptr = malloc(size); if (ptr) { allocated_blocks[ptr] = {size, type_name}; total_requested_allocated += size; type_allocation_counts[type_name]++; std::cout << "[ALLOC] " << size << " bytes (" << type_name << ") at " << ptr << "\n"; } return ptr; } // And update delete to decrement void operator delete(void* ptr) noexcept { if (!ptr) return; auto it = allocated_blocks.find(ptr); if (it != allocated_blocks.end()) { total_requested_freed += it->second.first; type_allocation_counts[it->second.second]--; std::cout << "[FREE] " << it->second.first << " bytes (" << it->second.second << ") at " << ptr << "\n"; allocated_blocks.erase(it); } free(ptr); }
Example Usage
int main() { // Track a raw int int* int_ptr = tracked_new<int>(); std::cout << "Total allocated: " << total_requested_allocated << " bytes\n"; // Track a custom object MyCustomObj* obj_ptr = tracked_new<MyCustomObj>(); std::cout << "Live MyCustomObj instances: " << MyCustomObj::get_live_count() << "\n"; // Cleanup delete int_ptr; delete obj_ptr; std::cout << "Total freed: " << total_requested_freed << " bytes\n"; std::cout << "Live MyCustomObj instances: " << MyCustomObj::get_live_count() << "\n"; return 0; }
- Thread Safety: If your code is multi-threaded, wrap access to the static tracking maps with a
std::mutexto avoid race conditions. - Allocation Edge Cases: Be aware of things like placement
new(you'll need to handle those separately if they're used) and compiler optimizations that might elide allocations. - Debug Mode vs Release: Some allocators add even more metadata in debug builds—this approach will still track your requested size regardless.
内容的提问来源于stack exchange,提问作者Derek Johnson

