C++多线程环境中进行内存分配跟踪的接口类设计(全局重载new/delete操作符)

通过全局重载newdelete操作符,实现堆区空间的分配和释放的跟踪记录

// Memory.h
#if TRACK_MEMORY
#ifdef PLATFORM_WINDOWS

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(size_t size);

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](size_t size);

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(size_t size, const char* desc);

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](size_t size, const char* desc);

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(size_t size, const char* file, int line);

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](size_t size, const char* file, int line);

void __CRTDECL operator delete(void* memory);
void __CRTDECL operator delete(void* memory, const char* desc);
void __CRTDECL operator delete(void* memory, const char* file, int line);
void __CRTDECL operator delete[](void* memory);
void __CRTDECL operator delete[](void* memory, const char* desc);
void __CRTDECL operator delete[](void* memory, const char* file, int line);

#define hnew new(__FILE__, __LINE__)	// 源文件、行号,用于跟踪进行内存分配的位置
#define hdelete delete

#else
	#warning "Memory tracking not available on non-Windows platform"
#define hnew new
#define hdelete delete

#endif

#else

#define hnew new
#define hdelete delete

#endif
// Memory.cpp
#if TRACK_MEMORY && PLATFORM_WINDOWS

// windows平台的MSVC编译器的标注和属性
_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR	
void* __CRTDECL operator new(size_t size)
{
	return Allocator::Allocate(size);	// 分配一块大小为 size 字节的内存。
}

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](size_t size)
{
	return Allocator::Allocate(size);	
}

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(size_t size, const char* desc)
{
	return Allocator::Allocate(size, desc);	// 分配一块大小为 size 字节的内存,并附带一个描述字符串。
}

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](size_t size, const char* desc)
{
	return Allocator::Allocate(size, desc);
}

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new(size_t size, const char* file, int line)
{
	return Allocator::Allocate(size, file, line);	// 分配一块大小为 size 字节的内存,并记录文件名和行号。
}

_NODISCARD _Ret_notnull_ _Post_writable_byte_size_(size) _VCRT_ALLOCATOR
void* __CRTDECL operator new[](size_t size, const char* file, int line)
{
	return Allocator::Allocate(size, file, line);
}

void __CRTDECL operator delete(void* memory)
{
	return Allocator::Free(memory);
}

void __CRTDECL operator delete(void* memory, const char* desc)
{
	return Allocator::Free(memory);
}

void __CRTDECL operator delete(void* memory, const char* file, int line)
{
	return Allocator::Free(memory);
}

void __CRTDECL operator delete[](void* memory)
{
	return Allocator::Free(memory);
}

void __CRTDECL operator delete[](void* memory, const char* desc)
{
	return Allocator::Free(memory);
}

void __CRTDECL operator delete[](void* memory, const char* file, int line)
{
	return Allocator::Free(memory);
}

#endif

自定义内存分配接口

// Memory.h
#pragma once

#include <map>
#include <mutex>

// 用于记录整个程序内存分配的情况
struct AllocationStats
{
	size_t TotalAllocated = 0;
	size_t TotalFreed = 0;
};

// 用于记录单个内存分配的信息
struct Allocation
{
	void* Memory = 0;
	size_t Size = 0;
	const char* Category = 0;	// 描述信息,比如记录申请内存分配的代码的位置,该内存的用处等等
};

// 对外接口,用于获取记录分配情况的静态对象(仅Memory.cpp可见)
namespace Memory
{
	const AllocationStats& GetAllocationStats();
}

// Map Allocator 自定义的内存分配器,用于管理std::map的键值对的内存分配,
template <class T>
struct Mallocator
{
	typedef T value_type;

	Mallocator() = default;
	template <class U> constexpr Mallocator(const Mallocator <U>&) noexcept {}

	T* allocate(std::size_t n)
	{
#undef max
		// 64位操作系统最大寻址内存值为2^64,因此要保证传入的n是小于这个的
		if (n > std::numeric_limits<std::size_t>::max() / sizeof(T))
			throw std::bad_array_new_length();

		if (auto p = static_cast<T*>(std::malloc(n * sizeof(T)))) {
			return p;
		}

		throw std::bad_alloc();
	}

	void deallocate(T* p, std::size_t n) noexcept {
		std::free(p);
	}
};


struct AllocatorData
{
	// 2个自定义分配器,分别用于管理std::map中的 这种键值对的内存分配: 
	// key:                   value:
	// const void* const  --  Allocation
	// const char* const  --  AllocationStats
	using MapAlloc = Mallocator<std::pair<const void* const, Allocation>>;
	using StatsMapAlloc = Mallocator<std::pair<const char* const, AllocationStats>>;

	using AllocationStatsMap = std::map<const char*, AllocationStats, std::less<const char*>, StatsMapAlloc>;

	// 两个std::map容器
	// key:内存地址;value: Allocation结构体,记录了内存的指针、大小、描述信息
	std::map<const void*, Allocation, std::less<const void*>, MapAlloc> m_AllocationMap;
	
	// key:描述信息;value: 内存总共分配数量、释放数量
	AllocationStatsMap m_AllocationStatsMap;
	
	std::mutex m_Mutex, m_StatsMutex;
};

// 内存分配器接口定义
class Allocator
{
public:
	static void Init();

	static void* AllocateRaw(size_t size);

	static void* Allocate(size_t size);
	static void* Allocate(size_t size, const char* desc);
	static void* Allocate(size_t size, const char* file, int line);
	static void Free(void* memory);

	static const AllocatorData::AllocationStatsMap& GetAllocationStats() { return s_Data->m_AllocationStatsMap; }
private:
	inline static AllocatorData* s_Data = nullptr;
};
#include "Memory.h"

#include <memory>
#include <map>
#include <mutex>

#include "Log.h"
// 用于记录全局内存分配、释放的信息
static Hazel::AllocationStats s_GlobalStats;

// 分配器是否正在进行初始化操作(应付多线程)
static bool s_InInit = false;

// 初始化阶段,主要是分配一个静态的AllocatorData对象(lazy 初始化)
void Allocator::Init()
{
	if (s_Data)
		return;

	s_InInit = true;
	AllocatorData* data = (AllocatorData*)Allocator::AllocateRaw(sizeof(AllocatorData));
	new(data) AllocatorData();	// 定位new(placement new)在指定地址构造目标对象,并调用构造函数初始化,释放需要调用operator delete
	s_Data = data;
	s_InInit = false;
}

// 利用malloc进行原始内存分配(即不会调用构造和析构),记得手动调用Allocator::free
void* Allocator::AllocateRaw(size_t size)
{
	return malloc(size);
}

void* Allocator::Allocate(size_t size)
{
	// 如果一个线程正在执行Init()函数,分配请求用原始内存分配来处理
	if (s_InInit)
		return AllocateRaw(size);

	if (!s_Data)
		Init();

	void* memory = malloc(size);

	{
		std::scoped_lock<std::mutex> lock(s_Data->m_Mutex);
		Allocation& alloc = s_Data->m_AllocationMap[memory];	// 没有该key就创建,有就返回
		alloc.Memory = memory;
		alloc.Size = size;
	
		s_GlobalStats.TotalAllocated += size;
	}

	return memory;
}

// 分配带有描述信息的内存,这个内存不仅要记录到总分配内存计数器中,还要把这种类型的内存单独进行计数
void* Allocator::Allocate(size_t size, const char* desc)
{
	if (!s_Data)
		Init();

	void* memory = malloc(size);

	{
		std::scoped_lock<std::mutex> lock(s_Data->m_Mutex);
		Allocation& alloc = s_Data->m_AllocationMap[memory];
		alloc.Memory = memory;
		alloc.Size = size;
		alloc.Category = desc;

		s_GlobalStats.TotalAllocated += size;
		if (desc)
			s_Data->m_AllocationStatsMap[desc].TotalAllocated += size; // 单独计数
	}
	
	return memory;
}
// line没用到,目前只想逐源文件记录内存分配量
void* Allocator::Allocate(size_t size, const char* file, int line)
{
	if (!s_Data)
		Init();

	void* memory = malloc(size);

	{
		std::scoped_lock<std::mutex> lock(s_Data->m_Mutex);
		Allocation& alloc = s_Data->m_AllocationMap[memory];
		alloc.Memory = memory;
		alloc.Size = size;
		alloc.Category = file;

		s_GlobalStats.TotalAllocated += size;
		s_Data->m_AllocationStatsMap.TotalAllocated += size;
	}

	return memory;
}

void Allocator::Free(void* memory)
{
	if (memory == nullptr)
		return;

	{
		// map中有,计数更新并移除
		bool found = false;
		{
			std::scoped_lock<std::mutex> lock(s_Data->m_Mutex);
			auto allocMapIt = s_Data->m_AllocationMap.find(memory);
			found = allocMapIt != s_Data->m_AllocationMap.end();
			if (found)	
			{
				const Allocation& alloc = allocMapIt->second;
				s_GlobalStats.TotalFreed += alloc.Size;
				if (alloc.Category)
					s_Data->m_AllocationStatsMap[alloc.Category].TotalFreed += alloc.Size;

				s_Data->m_AllocationMap.erase(memory);
			}
		}
		if (!found)
			LOG("Memory", "Memory block {0} not present in alloc map", memory);
	}
	
	free(memory);
}

namespace Memory {
	const AllocationStats& GetAllocationStats() { return s_GlobalStats; }
}