From 49023da0c166912b9a48528b9f61fd348035e85c Mon Sep 17 00:00:00 2001 From: _Redstone_c_ Date: Wed, 11 Jan 2023 19:24:02 +0800 Subject: [PATCH] feat(memory): add memory leak check assertion --- .../Source/Private/Memory/Memory.cpp | 124 +++++++++++++----- .../Source/Private/Testing/MemoryTesting.cpp | 1 + .../Source/Public/Memory/Memory.h | 1 + 3 files changed, 96 insertions(+), 30 deletions(-) diff --git a/Redcraft.Utility/Source/Private/Memory/Memory.cpp b/Redcraft.Utility/Source/Private/Memory/Memory.cpp index a42e669..f87835d 100644 --- a/Redcraft.Utility/Source/Private/Memory/Memory.cpp +++ b/Redcraft.Utility/Source/Private/Memory/Memory.cpp @@ -1,6 +1,8 @@ #include "Memory/Memory.h" #include "Memory/Alignment.h" +#include "Templates/Atomic.h" +#include "Templates/ScopeHelper.h" #include "Miscellaneous/AssertionMacros.h" #if PLATFORM_WINDOWS @@ -13,26 +15,72 @@ NAMESPACE_MODULE_BEGIN(Utility) NAMESPACE_BEGIN(Memory) +#if DO_CHECK + +class FMemoryLeakChecker +{ +private: + + TAtomic MemoryAllocationCount; + +public: + + FORCEINLINE constexpr FMemoryLeakChecker() + : MemoryAllocationCount(0) + { } + + FORCEINLINE ~FMemoryLeakChecker() + { + checkf(MemoryAllocationCount.Load() == 0, TEXT("There is unfree memory. Please check for memory leaks.")); + } + + FORCEINLINE void AddMemoryAllocationCount() + { + MemoryAllocationCount.FetchAdd(1, EMemoryOrder::Relaxed); + } + + FORCEINLINE void ReleaseMemoryAllocationCount() + { + MemoryAllocationCount.FetchSub(1, EMemoryOrder::Relaxed); + } + +}; + +FMemoryLeakChecker MemoryLeakChecker; + +#endif + void* Malloc(size_t Count, size_t Alignment) { checkf(IsValidAlignment(Alignment), TEXT("The alignment value must be an integer power of 2.")); + Count = Count != 0 ? Count : 1; // Treat zero-byte allocation as one-byte allocation. + const size_t MinimumAlignment = Count >= 16 ? 16 : 8; Alignment = MinimumAlignment > Alignment ? MinimumAlignment : Alignment; void* Result = nullptr; -#if PLATFORM_WINDOWS - if (Count != 0) Result = _aligned_malloc(Count, Alignment); -#else - void* Ptr = SystemMalloc(Count + Alignment + sizeof(void*) + sizeof(size_t)); - if (Ptr) +# if PLATFORM_WINDOWS { - Result = Align(reinterpret_cast(Ptr) + sizeof(void*) + sizeof(size_t), Alignment); - *reinterpret_cast(reinterpret_cast(Result) - sizeof(void*)) = Ptr; - *reinterpret_cast(reinterpret_cast(Result) - sizeof(void*) - sizeof(size_t)) = Count; + Result = _aligned_malloc(Count, Alignment); } -#endif +# else + { + void* Ptr = SystemMalloc(Count + Alignment + sizeof(void*) + sizeof(size_t)); + + if (Ptr != nullptr) + { + Result = Align(reinterpret_cast(Ptr) + sizeof(void*) + sizeof(size_t), Alignment); + *reinterpret_cast(reinterpret_cast(Result) - sizeof(void*)) = Ptr; + *reinterpret_cast(reinterpret_cast(Result) - sizeof(void*) - sizeof(size_t)) = Count; + } + } +# endif + + check(Result != nullptr); + + check_code({ MemoryLeakChecker.AddMemoryAllocationCount(); }); return Result; } @@ -41,39 +89,55 @@ void* Realloc(void* Ptr, size_t Count, size_t Alignment) { checkf(IsValidAlignment(Alignment), TEXT("The alignment value must be an integer power of 2.")); + Count = Count != 0 ? Count : 1; // Treat zero-byte allocation as one-byte allocation. + const size_t MinimumAlignment = Count >= 16 ? 16 : 8; Alignment = MinimumAlignment > Alignment ? MinimumAlignment : Alignment; - if (Ptr && Count) + void* Result = nullptr; + + if (Ptr != nullptr) { -#if PLATFORM_WINDOWS - return _aligned_realloc(Ptr, Count, Alignment); -#else - void* Result = Malloc(Count, Alignment); - size_t PtrSize = *reinterpret_cast(reinterpret_cast(Ptr) - sizeof(void*) - sizeof(size_t)); - Memcpy(Result, Ptr, Count < PtrSize ? Count : PtrSize); - Free(Ptr); - return Result; -#endif - } - else if (Ptr == nullptr) - { - return Malloc(Count, Alignment); +# if PLATFORM_WINDOWS + { + Result = _aligned_realloc(Ptr, Count, Alignment); + } +# else + { + Result = Malloc(Count, Alignment); + + if (Result != nullptr) + { + size_t PtrSize = *reinterpret_cast(reinterpret_cast(Ptr) - sizeof(void*) - sizeof(size_t)); + Memcpy(Result, Ptr, Count < PtrSize ? Count : PtrSize); + Free(Ptr); + } + } +# endif } else { - Free(Ptr); - return nullptr; + Result = Malloc(Count, Alignment); } + + check(Result != nullptr); + + return Result; } void Free(void* Ptr) { -#if PLATFORM_WINDOWS - _aligned_free(Ptr); -#else - SystemFree(*reinterpret_cast(reinterpret_cast(Ptr) - sizeof(void*))); -#endif +# if PLATFORM_WINDOWS + { + _aligned_free(Ptr); + } +# else + { + SystemFree(*reinterpret_cast(reinterpret_cast(Ptr) - sizeof(void*))); + } +# endif + + check_code({ if (Ptr != nullptr) MemoryLeakChecker.ReleaseMemoryAllocationCount(); }); } size_t QuantizeSize(size_t Count, size_t Alignment) diff --git a/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp b/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp index ecedd26..023ad5b 100644 --- a/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp +++ b/Redcraft.Utility/Source/Private/Testing/MemoryTesting.cpp @@ -148,6 +148,7 @@ void TestMemoryMalloc() always_check(PtrC->A == 0x01234567); delete [] PtrC; + Memory::Free(Memory::Realloc(Memory::Malloc(0), 0)); } NAMESPACE_UNNAMED_BEGIN diff --git a/Redcraft.Utility/Source/Public/Memory/Memory.h b/Redcraft.Utility/Source/Public/Memory/Memory.h index e4d5cff..5404e7b 100644 --- a/Redcraft.Utility/Source/Public/Memory/Memory.h +++ b/Redcraft.Utility/Source/Public/Memory/Memory.h @@ -255,6 +255,7 @@ NODISCARD REDCRAFTUTILITY_API void* Malloc(size_t Count, size_t Alignment = Defa /** * Reallocates the given area of memory. It must be previously allocated by Malloc() or Realloc(). + * If 'Ptr' is a nullptr, effectively the same as calling Malloc(). * * @param Ptr - The pointer to the memory area to be reallocated. * @param Count - The number of bytes to allocate.