Detecting memory leaks in Visual C++ (Windows)

2019-04-17 06:17发布

问题:

I'm working on a large C++ project under Visual Studio 2010 and think that there are some memory leaks inside. I tried the approach with including crtdbg.h but it does not help much as I don't see where the leaks occured. Defining new has 2 pitfalls: First it needs to be done in every cpp file which is not really an option, and 2nd it breaks with e.g. Boost. Using new(nothrow) or anything that uses boosts "has_new_operator.h" breaks this. [EDIT: It fails to compile as the redefined "new" has no overloads for something like "nothrow" or the "boost magic"] (Unless one defines "new" after all boost headers including headers referencing boost)

Last but not least: I have singletons. They are implemented using subclasses of the singleton template and a static function variable. One of them is a config container where one registers settings (pairs of strings and ints that are than stored in maps) Since the mem leak dump is called before deallocation of the singleton instance I get a massive amount of leaks for all those strings and the singleton itself.

Any way to have only the real leaks shown or make it dump after static object deallocation?

Which free tools can handle this case?

回答1:

I have used the Visual Leak Detector with quite positive results. It is small and neat, and can be built into your project (assuming you have a running Debug configuration) in a matter of seconds:

https://vld.codeplex.com/

If set-up correctly (which can be done using the installer) then you only have to

#include <vld.h> 

in one of your .cpp files for each module - that's it, the header will do the linking for you. You don't have to put it everywhere. Internally the tool uses the CrtDbg, so you have to have a debug build running in order for it to work.

It gives you debugger or text output after each run (if configured using a config file), even when not run through a debugger. It is not the most powerfull tool, but these usually cost some coin ;)

EDIT: There is a possibility to enable the VLD also in non-debug configurations by defining VLD_FORCE_ENABLE before including the header. But the results may be tempered with then.

EDIT: I have tried a fresh installation of VLD. Note that for VS2013 compilers the v2.4rc2 version must be used (or anything greater v2.3). Version v2.3 only works up until VS2010 compilers.

After installation I created a new project and set-up my include- and library-directories to include the respective VLD folders. After that I used the following code to test memleak reports of singletons (note that this code doesn't make sense, it just proves a point):

#include <iostream>
#include <string>
#include <sstream>
#include <map>

// Uncomment this, if you want VLD to work in non-debug configurations
//#define VLD_FORCE_ENABLE

#include <vld.h>

class FooSingleton {
    private:
        std::map<std::string, std::string*>
            _map;

        FooSingleton() {
        }

    public:
        static FooSingleton* getInstance(void) {
            /* THIS WOULD CAUSE LEAKS TO BE DETECTED
               SINCE THE DESTRUCTOR WILL NEVER BE CALLEd
               AND THE MAP IS NOT CLEARED.
            */
            // FooSingleton* instance = new FooSingleton;
            // return instance;

            static FooSingleton instance;
            return &instance;
        }

        void addString(const std::string& val) {
            _map.insert(std::make_pair(val, new std::string(val)));
        }

        ~FooSingleton(void) {
            auto it = _map.begin();
            auto ite = _map.end();

            for(; it != ite; ++it) {
                delete it->second;
            }
        }
};

int main(int argc, char** argv) {
    FooSingleton* fs = FooSingleton::getInstance();
    for(int i = 0; i < 100; ++i) {
        std::stringstream ss;
        ss << i << "nth string.";
        fs->addString(ss.str());
    }

    return 0;
}

With this code, the VLD does not report any leaks because the static auto-variable in getInstance() will be destructed upon exit and the elements in the map will be deleted. This must be done nevertheless, even if it's a singleton, otherwise the leaks will be reported. But in this case:

Visual Leak Detector Version 2.3 installed.
Aggregating duplicate leaks.
Outputting the report to the debugger and to D:\dev\projects\tmp\memleak\memleak\memory_leak_report.txt
No memory leaks detected. Visual Leak Detector is now exiting.

If the code in the getInstance() is changed to the commented version, then the singleton is never cleared up and the following leaks (amongst others) is reported:

---------- Block 11 at 0x008E5928: 52 bytes ----------
Leak Hash: 0x973608A9 Count: 100
  Call Stack:
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory (36): memleak.exe!std::_Allocate<std::_Tree_nod<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::alloca + 0x15 bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xmemory (187): memleak.exe!std::allocator<std::_Tree_nod<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::alloca + 0xB bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xtree (560): memleak.exe!std::_Tree_val<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,s + 0xD bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xtree (588): memleak.exe!std::_Tree_val<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,s + 0x8 bytes
    c:\program files (x86)\microsoft visual studio 10.0\vc\include\xtree (756): memleak.exe!std::_Tree<std::_Tmap_traits<std::basic_string<char,std::char_traits<char>,std::allocator<char> >,std::basic_string<char,std::char_traits<char>,std::allocator<char> > *,std::less<std::basic_string<char,std::char_traits<char>,std::allocator<char> > >,std:: + 0x17 bytes
    d:\dev\projects\tmp\memleak\memleak\main.cpp (33): memleak.exe!FooSingleton::addString + 0xA9 bytes
    d:\dev\projects\tmp\memleak\memleak\main.cpp (51): memleak.exe!main + 0x37 bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (555): memleak.exe!__tmainCRTStartup + 0x19 bytes
    f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c (371): memleak.exe!mainCRTStartup
    0x76BF919F (File and line number not available): KERNEL32.DLL!BaseThreadInitThunk + 0xE bytes
    0x7739A22B (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x84 bytes
    0x7739A201 (File and line number not available): ntdll.dll!RtlInitializeExceptionChain + 0x5A bytes
  Data:
    C0 53 8E 00    30 67 8E 00    C0 53 8E 00    98 58 8E 00     .S..0g.. .S...X..
    30 6E 74 68    20 73 74 72    69 6E 67 2E    00 CD CD CD     0nth.str ing.....
    0C 00 00 00    0F 00 00 00    CD CD CD CD    48 56 8E 00     ........ ....HV..
    01 00 CD CD    

You can clearly see the Count: 100 for this block of code, which is correct.

I also edited my vld.ini file in the installation directory to have the following set to be enabled:

AggregateDuplicates = yes
ReportTo = both

These make sure that a) all duplicate leaks are squashed together to one report with a leak-count (as above, otherwise there would be 100 entries) and the other so that a report-file is dumped in the directory of the application.

So for singletons it works fine as long as you use the static auto-variable approach you are using and do your cleanup in the destructor.

EDIT: Also, the instrumentation can be disabled at specific code pieces. If the above code would be modified like this:

void addString(const std::string& val) {
    VLDDisable();
    _map.insert(std::make_pair(val, new std::string(val)));
    VLDEnable();
}

The leaks will never be profiled and not tracked.



回答2:

You can get memory leaks source from crtdebug. it won't help you with the boost allocations, unless you compile boost (or any library) in the same way, but for the rest, it will show you allocation file and line.

This is how you use properly the crtdebug.h:

in the top of your stdafx.h (or any PCH file) add the following lines:

#ifdef DEBUG
    //must define both _CRTDBG_MAP_ALLOC and _CRTDBG_MAP_ALLOC_NEW
    #define _CRTDBG_MAP_ALLOC
    #define _CRTDBG_MAP_ALLOC_NEW

    #include <stdlib.h>
    #include <crtdbg.h>
    //if you won't use this macro you'll get all new as called from crtdbg.h      
    #define DEBUG_NEW   new( _CLIENT_BLOCK, __FILE__, __LINE__)
    #define new DEBUG_NEW
#endif

Now in the beginning of your main or winmain or any entry point to your program add the following lines:

//register memory leak check at end of execution: 
//(if you use this you won't need to use _CrtDumpMemoryLeaks at the end of your main)
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
//set report mode:
_CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

Now here a small test I've made:

After a new console program from VS10 called "test": My stdafx.h:

#pragma once

#ifdef _DEBUG
  #define _CRTDBG_MAP_ALLOC
  #define _CRTDBG_MAP_ALLOC_NEW
  #include <stdlib.h>
  #include <crtdbg.h>

  #define DEBUG_NEW   new( _CLIENT_BLOCK, __FILE__, __LINE__)
  #define new DEBUG_NEW
#endif

#include "targetver.h"

#include <stdio.h>
#include <tchar.h> 

and my test.cpp is:

#include "stdafx.h"
void CheckMemoryLeak()
{
    char *ptr=new char[100];
    int n=900;
    sprintf(ptr,"%d",n);
}

int _tmain(int argc, _TCHAR* argv[])
{
    _CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
    _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_DEBUG );

    CheckMemoryLeak();

    return 0;
}

Output is:

'tests.exe': Loaded 'C:\Users\shr\Documents\Visual Studio 2010\Projects\tests\Debug\tests.exe', Symbols loaded.
'tests.exe': Loaded 'C:\Windows\SysWOW64\ntdll.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:\Windows\SysWOW64\kernel32.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:\Windows\SysWOW64\KernelBase.dll', Cannot find or open the PDB file
'tests.exe': Loaded 'C:\Windows\SysWOW64\msvcr100d.dll', Symbols loaded.
Detected memory leaks!
Dumping objects ->
c:\users\shr\documents\visual studio 2010\projects\tests\tests\tests.cpp(9) : {97} client block at 0x01003288, subtype 0, 100 bytes long.
 Data: <900             > 39 30 30 00 CD CD CD CD CD CD CD CD CD CD CD CD 
Object dump complete.
The program '[1600] tests.exe: Native' has exited with code 0 (0x0).