0

I am primarily a Linux developer however, I have inherited a windows dll with a memory leak. I know the cause and believe I have fixed it. I would like to check this in the unit tests. The unit tests use the builtin cppunit test framework, which is no relation to the cppunit framework I normally use on Linux. i.e.

#include "CppUnitTest.h"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;

What I would like to do is measure the memory usage before and after a block of code and check that it has not changed - which would indicate a memory leak. Or similarly, to check that an allocator type function allocates exactly the amount of memory that a subsequent destructor type function frees.

Is there suitable API I can use to reliably get the current memory usage?

I naively tried the following:

size_t getMemoryUsage()
{
    PROCESS_MEMORY_COUNTERS pmc;
    auto processHandle = GetCurrentProcess();
    if (GetProcessMemoryInfo(processHandle, &pmc, sizeof(pmc)))
    {
        return pmc.WorkingSetSize;
    }
    else 
    {
        Assert::Fail(L"Unable to get memory usage for current process");
    }
    return 0;
}

This gives me the memory usage of the current process. Unfortunately this does not accurately reflect the allocations and frees going on. I think that if I free memory the OS may still hold it in reserve for the application to use later. The working set is the OS's allocation to the process not the memory it actually uses internally.

I tried changing this to PrivateUsage via What is private bytes, virtual bytes, working set? but this does not always seem to change after a malloc.

Is there a suitable API which will do this for me? Perhaps a library that can substitute an instrumented malloc like you might do with LD_PRELOAD on Linux? See ld-preload-equivalent-for-windows-to-preload-shared-libraries

There are several similar questions here - for example memory leak unit test c++.

This question is specific to the case of unit testing a DLL using cppunit in visual studio.

The DLL does not expose an interface for a allocator that could be overridden. I think my best option at present may be to add one. I would rather avoid making extensive changes if there is a simpler way. An answer confirming that this is the only way will be accepted.

Bruce Adams
  • 4,953
  • 4
  • 48
  • 111

2 Answers2

3

I don't feel that trying to use the OS API's is reliable, as if I do p = new char[1024]; delete[] p; there is no promise that memory is returned to the OS, and in many cases it won't be. e.g. say the smallest page size is 4KB, well obviously handing out 4KB for small objects would be wasteful, so the allocator inside your process will split this larger chunks up, and thus the OS can't see if such pieces are freed or not.

The applies to other OS/compilers as well. You can determine a trend over time if you keep repeating the same test loop "it keeps using more memory", but then you have to go searching for it, and with less-consistent loads, hard to tell if a few KB difference is a leak or not.


Visual Studio has a number of more integrated tools to help. These generally assume you are using new/delete, malloc/free, or other such things the IDE could potentially know about. If you are not, you might need to adjust your DLL slightly so it is possible for the IDE to know what is going on in the most accurate way.

If for example you use an internal memory "pool", the system can only know that the pool allocated memory, not what it was used for or if it was returned to that pool.


To find a memory leak within an execution (say running a test case), you can use the memory leak detection feature.

#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
int main()
{
    _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
    char *leak = new char[1024];
}
The thread 0x3280 has exited with code 0 (0x0).
Detected memory leaks!
Dumping objects ->
{94} normal block at 0x0000021EF2EA1FD0, 1024 bytes long.
 Data:  CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.
The program '[0xF64] test.exe' has exited with code 0 (0x0).

You can then set a break point to stop on that allocation next time to find it. You will need the program to run exactly the same for this to work, but for a unit test on a piece of suspect code that is generally doable.

You can do this by pausing your program at the very start in VS, then in the watch window setting {,,ucrtbased.dll}_crtBreakAlloc to the desired allocation number, say 94. Running your program it will stop inside the allocation in question, letting you see the stack trace etc.

By default this goes to the debug output, which is not easy to catch from automation, but you can redirect it to say stderr, then check if there is any "Detected memory leaks!" in your test output (along with test case success/fail/etc.).

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR);
_CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR);

You can also include the file and line number (see the Microsoft docs), but this is often more difficult unfortunately, as the macro can break for example placement new, and doesn't work where you are not directly allocating in the source you are compiling (existing libraries, etc.).


A really useful IDE tool is the Memory Usage in the diagnostic tools window.

Take for example:

#include <stdio.h>
int main()
{
    for (int i = 0; i < 1000; ++i)
    {
        char *more_leaks = new char[100];
        sprintf(more_leaks, "Leaking %d memory", i);
        if (i % 55) delete[] more_leaks;
    }
}

In VS, goto "Debug" -> "Windows" -> "Diagnostic Tools".

Run your program up to some breakpoint, and click the "Memory Usage" tab in the new window. On the left of the tab is a "Take Snapshot" button. Then run your program to after the function you think leaks, and take another snapshot.

You can then see if there was a difference in the allocations, what they are, what data they hold, and generally explore the memory in your program.

enter image description here

Fire Lancer
  • 29,364
  • 31
  • 116
  • 182
  • I have the same feeling about the OS APIs hence the question. While the tools you mention are useful for debugging I'm looking for test automation here so they don't really solve the problem. – Bruce Adams Dec 19 '19 at 12:20
  • Well with the memory leak detection dumping the information, and you can configure it with `_CRTDBG_FILE_STDERR` then I just check there are no leaks along with no test failures in my automation in the process output ("if output contains 'Detected memory leaks!' then fail"). Then use the other tools to find what the unit test output was actually complaining about. – Fire Lancer Dec 19 '19 at 13:37
  • I tried _CrtSetDbgFlag and _CrtSetReportMode but I can't seem to get any output for it from the test code even if I introduce a leak. – Bruce Adams Dec 19 '19 at 17:02
  • Make sure used the debug CRT, and you have the needed `_CRTDBG_MAP_ALLOC` and `#include ` in every source file you are checking for C allocations (`malloc`, etc.). `new` I think is global. Remember `_CRTDBG_LEAK_CHECK_DF` means to report only when the process exits, as it is difficult to tell leak vs non-leak with an existing process automatically anyway. And `_CRTDBG_ALLOC_MEM_DF` needs to be before you allocate stuff, be careful of static/global initializers, they might not be detected. – Fire Lancer Dec 19 '19 at 17:47
  • Oh and don't forget `_CrtSetReportFile` if you wanted it on stdout/stderr rather than the VS debug "Output" window. – Fire Lancer Dec 19 '19 at 17:49
0

It was probably overkill but I ended up going the route of using local implementations of malloc and free and allowing them to be overridden:

foobar_alloc.h:

///
/// @brief
/// This module provides routines to control the memory allocation and free functions 
/// used by the library.
///
/// Altering these functions is intended for use in testing only.
///

#ifdef __cplusplus
extern "C"
{
#endif

///
/// @brief
/// A function to be used as allocate memory
/// This should have semantics equivalent to the malloc() system call.
typedef void* (*AllocFunc)(size_t);

///
/// @brief
/// A function to be used to delete memory
/// This should have semantics equivalent to the free() system call.
typedef void (*FreeFunc)(void*);

///
/// @brief
/// Tells the library to use the given allocator function instead of the current one.
///
/// The default value is to use malloc()
/// 
/// @return
/// returns the currently used allocator function allowing it to be restored
///
__declspec(dllexport) AllocFunc set_alloc_func(AllocFunc func);

///
/// @brief
/// Tells the library to use the given deallocator function instead of the current one.
///
/// The default value is to use free()
/// 
/// @return
/// returns the currently used deallocator function allowing it to be restored
///
__declspec(dllexport) FreeFunc set_free_func(FreeFunc func);

///
/// @brief
/// Allocate memory using the currently set allocation funtion - default malloc()
///
__declspec(dllexport) void* foobar_malloc(size_t size);

///
/// @brief
/// Allocate memory using the currently set allocation funtion - default malloc()
///
__declspec(dllexport) void foobar_free(void* ptr);

#ifdef __cplusplus
}
#endif

foobar_alloc.cpp:

#include "foobar_alloc.h"
#include <malloc.h>

static AllocFunc allocator = malloc;
static FreeFunc deallocator = free;

AllocFunc set_alloc_func(AllocFunc newFunc)
{
    AllocFunc old = allocator;
    allocator = newFunc;
    return old;
}

FreeFunc set_free_func(FreeFunc newFunc)
{
    FreeFunc old = deallocator;
    deallocator = newFunc;
    return old;     
}

void* foobar_malloc(size_t size)
{
    return allocator(size);
}

void foobar_free(void* ptr)
{
    return deallocator(ptr);
}

// EOF

To allow for C++ code as well as C I added,

foobar_new.h:

#pragma once
///
/// @brief
/// This is a private header file to override the global new and delete operators
/// to use foobar_malloc and foobar_free to allow for testing.
/// It is not intended for use outside of the dll.
/// 

#include <new>
#include "foobar_alloc.h"

void* operator new(std::size_t sz) {
    return foobar_malloc(sz);
}

void operator delete(void* ptr) noexcept {
    foobar_free(ptr);
}

void* operator new[](std::size_t sz) {
    return foobar_malloc(sz);
}

void operator delete[](void* ptr) noexcept {
    foobar_free(ptr);
}      

Add your own implementation of malloc and free in the test code, for example:

Test_foobar.cpp:

namespace
{
    size_t totalMemoryUsage = 0;
}

// override the global operator new 
void* leaktest_malloc(std::size_t n) throw(std::bad_alloc)
{
    void* res = malloc(n);
    if (res == nullptr) throw std::bad_alloc();
    totalMemoryUsage += _msize(res);
    return res;
}

// override the global delete operator 
void leaktest_free(void* p) throw()
{
    totalMemoryUsage -= _msize(p);
    free(p);
}

Remembering to enable it in your tests somewhere:

// tell the library to use our wrappers to free & malloc
set_alloc_func(leaktest_malloc);
set_free_func(leaktest_free);

You need to #include foobar_new in all C++ code in the DLL that allocates and replace malloc and free with foobar_malloc and foobar_free.

Replace foobar with your own library name as appropriate to avoid namespace issues with any other libraries.

Bruce Adams
  • 4,953
  • 4
  • 48
  • 111