Memory Leaks

index -|- home

in-page links: preamble functions results missing linking download alternatives summary end

PREAMBLE

As the background image indicates, this is all in Microsoft (MS) Windows XP, using the MS tools. It has been a while since I 'tested' the MS debug memory stuff, built in to MSVC headers, so I tried it on a 'testmem' application I created. It was easy to make the required declare and includes :-

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

This was easy since initially _EVERY_ module in this project, each only has ONE header included - namely "testmem.h" - where _ALL_ the other needed includes are done. As you can see I really follow an include-one-include-all windows idea to a tee ;=)) But in this case, later I added some other modules, which do have different includes, but always starting with the above set.

But on this first test, this did NOTHING visible.

I then found the function,
_CrtSetDbgFlag( newFlag );
that can alter, add to the 'report' output, and one such flag imbeds an automatic report call on exit ... it seems when I had 'seen' an automatic reports in the past, that project had been setting this flag at start up ...

The minimum flags needed, to get a 'report' of leaks at the end were :-
#define newFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF )


top

MEMORY FUNCTIONS

In Windows we have a VARIETY of memory functions that can be used, and I tried each :-

# Function Description

i.

malloc/free

as per unix/POSIX

ii.

new/delete

in MS C++

iii.

LocalAlloc/LocalFree

from the local heap

iv.

VirtualAlloc/VirtualFree

for BIG (> 2 MB) allocations

v.

GlobalAlloc/GlobalFree

can be SHARED inter-process, so is used for DDE (Dynamic Data Exchange), the clipboard functions, and OLE data objects.

I deliberately tested all five(5) functions in my testmem - a C++ application - which has no other purpose other than this test ... and I added my own, often used, sprtf() function, which writes debug information to a log file ...

You can read in certain MS articles, like -
http://msdn2.microsoft.com/en-us/library/ms810603.aspx
where it says - "Subsequent memory allocations via malloc, GlobalAlloc, and LocalAlloc return pointers to memory allocated from the same heap.", but this does NOT seem to be borne out in subsequent tests ... where the pointer returned from malloc and new were always in a different address range those from GlobalAlloc and LocalAlloc

I compiled, and ran the application in MSVC6 (1998), MSVC7.1 (2003) and MSVC8 (2005) ... I tried the application using the static RUNTIME, /MTd, and using the DLL RUNTIME, /MDd, and found that the /MTd static runtime also does not report memory leaks in a project modules linked through a DLL. But if a project consists only of static linked libraries, then /MTd works fine ...

Of course, since the memory leak report is only available in Debug configuration, only the Debug configuration was built.


top

RESULTS

This is the entries in the log file - it shows where the five(5) allocations were made :-

c:\gtools\tools\testmem\testmem.cpp(30): using new BYTE[1234]  Addr: 00272850
c:\gtools\tools\testmem\testmem.cpp(36): using malloc 4567  Addr: 00276E08
c:\gtools\tools\testmem\testmem.cpp(43): using LocalAlloc 2345  Addr: 00153E00
c:\gtools\tools\testmem\testmem.cpp(56): using GlobalAlloc 3456  Addr: 00154748
c:\gtools\tools\testmem\testmem.cpp(49): using VirtualAlloc 3 MB  Addr: 00260000

While MSVC7.1 always showed the file name where the un-freed allocation was made in the 'leak' report, MSVC8 sometimes failed. This is the MSVC8 output, to the output pane -

Dumping objects ->
c:\gtools\tools\testmem\testmem.cpp(36) : {56} normal block at 0x00276E08, 4567 bytes long.
 Data: < Addr: 00276E08 > 20 41 64 64 72 3A 20 30 30 32 37 36 45 30 38 00
{55} normal block at 0x00272850, 1234 bytes long.
 Data: < Addr: 00272850 > 20 41 64 64 72 3A 20 30 30 32 37 32 38 35 30 00
Object dump complete.

It is possible to have this output written to a log file, and I later used this so I could have a permanent record of the results. ...


top

MISSING

It can be seen that a number of allocations made are MISSING from the report

(a) Note it ONLY reports on memory allocated with malloc or new, and seems to ignore memory allocated using LocalAlloc (2345), VirtualAlloc (3 MB), and GlobalAlloc (3456) ... this is NOT a very good result.

(b) The allocation using 'new' does NOT show the module where it happened.

OVERALL, NOT A GOOD RESULT ;=((

Of course, you can add the flag _CRTDBG_CHECK_CRT_DF to ALSO check for leaks in the CRT code. This certainly expands the list, but includes too many things NOT in your code -

Detected memory leaks!
Dumping objects ->
c:\gtools\tools\testmem\testmem.cpp(40) : {56} normal block at 0x00A06E08, 4567 bytes long.
 Data: < Addr: 00A06E08 > 20 41 64 64 72 3A 20 30 30 41 30 36 45 30 38 00
{55} normal block at 0x00A02850, 1234 bytes long.
 Data: < Addr: 00A02850 > 20 41 64 64 72 3A 20 30 30 41 30 32 38 35 30 00
_getbuf.c(58) : {54} crt block at 0x00A05DC8, subtype 0, 4096 bytes long.
 Data: <c:\gtools\tools\> 63 3A 5C 67 74 6F 6F 6C 73 5C 74 6F 6F 6C 73 5C
mlock.c(279) : {53} crt block at 0x00A05D70, subtype 0, 24 bytes long.
 Data: < 0              > F8 30 15 00 FF FF FF FF 00 00 00 00 00 00 00 00
mlock.c(279) : {52} crt block at 0x00A05D18, subtype 0, 24 bytes long.
 Data: < 0              > C0 30 15 00 FF FF FF FF 00 00 00 00 00 00 00 00
mbctype.c(593) : {4} crt block at 0x00A037F0, subtype 0, 544 bytes long.
 Data: <                > 02 00 00 00 E4 04 00 00 00 00 00 00 00 00 00 00
ioinit.c(136) : {2} crt block at 0x00A02110, subtype 0, 1792 bytes long.
 Data: <                > FE FF FF FF C1 0A 00 00 00 00 00 00 00 00 00 00
tidtable.c(427) : {1} crt block at 0x00A01EC0, subtype 0, 532 bytes long.
 Data: <$               > 24 1D 00 00 FF FF FF FF 00 00 00 00 00 00 00 00
Object dump complete.

Notice there are items that are NOT in your code, and it reports module name you may never have heard of ;=()


top

STATIC versus DYNAMIC LINKING

Here I am NOT talking about the RUNTIME (/MTd versus /MDd), but functions in other third party libraries. Often such third party libraries can be used in either a STATIC or DYNAMIC linked form. Is there a difference using such external libraries linked in different ways, and is this effected by which RUNTIME used?

So, to my testmem project I add a memTestDLL module, and a memTestStatic module. The first built a DLL to later be dynamically linked, while the second was in a STATIC library. So I did the same five memory allocations in MAIN, DLL and STATIC components - total 15 allocations.

When compiled and linked, using the DLL RUNTIME (/MDd), and run the output to my log file was :-

Detected memory leaks!
Dumping objects ->
c:\gtools\tools\testmem\memteststatic.cpp(25) : {73} normal block at 0x00AA97E8, 4567 bytes long.
 Data: <00AA97E8        > 30 30 41 41 39 37 45 38 00 CD CD CD CD CD CD CD
{72} normal block at 0x00AA92D8, 1234 bytes long.
 Data: <00AA92D8        > 30 30 41 41 39 32 44 38 00 CD CD CD CD CD CD CD
c:\gtools\tools\testmem\memtestdll.cpp(60) : {64} normal block at 0x00AA80C0, 4567 bytes long.
 Data: <00AA80C0        > 30 30 41 41 38 30 43 30 00 CD CD CD CD CD CD CD
{61} normal block at 0x00AA2E78, 1234 bytes long.
 Data: <00AA2E78        > 30 30 41 41 32 45 37 38 00 CD CD CD CD CD CD CD
c:\gtools\tools\testmem\testmem.cpp(42) : {58} normal block at 0x00AA6EA8, 4567 bytes long.
 Data: <00AA6EA8        > 30 30 41 41 36 45 41 38 00 CD CD CD CD CD CD CD
{57} normal block at 0x00AA2850, 1234 bytes long.
 Data: <00AA2850        > 30 30 41 41 32 38 35 30 00 CD CD CD CD CD CD CD
Object dump complete.

From the LOG -

RUNTIME: Multithreaded (DLL) DEBUG (/MDd)
Allocations made in MAIN module: c:\gtools\tools\testmem\testmem.cpp ...
c:\gtools\tools\testmem\testmem.cpp(36): using new BYTE[1234]  00AA2850
c:\gtools\tools\testmem\testmem.cpp(42): using malloc 4567  00AA6EA8
c:\gtools\tools\testmem\testmem.cpp(49): using LocalAlloc 2345  00154FD8
c:\gtools\tools\testmem\testmem.cpp(62): using GlobalAlloc 3456  00155920
c:\gtools\tools\testmem\testmem.cpp(55): using VirtualAlloc 3 MB  00260000
Allocations made in DLL module: c:\gtools\tools\testmem\memtestdll.cpp ...
c:\gtools\tools\testmem\memtestdll.cpp(54): using new BYTE[1234]  00AA2E78
c:\gtools\tools\testmem\memtestdll.cpp(60): using malloc 4567  00AA80C0
c:\gtools\tools\testmem\memtestdll.cpp(67): using LocalAlloc 2345  001566B8
c:\gtools\tools\testmem\memtestdll.cpp(80): using GlobalAlloc 3456  00157008
c:\gtools\tools\testmem\memtestdll.cpp(73): using VirtualAlloc 3 MB  00270000
Allocations made in STATIC module: c:\gtools\tools\testmem\memteststatic.cpp ...
c:\gtools\tools\testmem\memteststatic.cpp(19): using new BYTE[1234]  00AA92D8
c:\gtools\tools\testmem\memteststatic.cpp(25): using malloc 4567  00AA97E8
c:\gtools\tools\testmem\memteststatic.cpp(32): using LocalAlloc 2345  00157DA0
c:\gtools\tools\testmem\memteststatic.cpp(45): using GlobalAlloc 3456  001586E8
c:\gtools\tools\testmem\memteststatic.cpp(38): using VirtualAlloc 3 MB  00280000

As suggested, what does NOT show are those buffers allocated using -
iii. LocalAlloc (2345),
iv. VirtualAlloc (3MB), nor
v. GlobalAlloc (3456) ...
so it seems these are NOT included in the 'Memory Leak' checking process ;=((

And using the 'static' RUNTIME (/MTd), the allocations made in the DLL are also not reported.

Detected memory leaks!
Dumping objects ->
c:\gtools\tools\testmem\memteststatic.cpp(25) : {58} normal block at 0x00A58000, 4567 bytes long.
 Data: <00A58000        > 30 30 41 35 38 30 30 30 00 CD CD CD CD CD CD CD
{57} normal block at 0x00A52D60, 1234 bytes long.
 Data: <00A52D60        > 30 30 41 35 32 44 36 30 00 CD CD CD CD CD CD CD
c:\gtools\tools\testmem\testmem.cpp(42) : {56} normal block at 0x00A56DE8, 4567 bytes long.
 Data: <00A56DE8        > 30 30 41 35 36 44 45 38 00 CD CD CD CD CD CD CD
{55} normal block at 0x00A52850, 1234 bytes long.
 Data: <00A52850        > 30 30 41 35 32 38 35 30 00 CD CD CD CD CD CD CD
Object dump complete.

And from my log file ...

RUNTIME: Multithreaded (static) DEBUG (/MTd)
Allocations made in MAIN module: c:\gtools\tools\testmem\testmem.cpp ...
c:\gtools\tools\testmem\testmem.cpp(36): using new BYTE[1234]  00A52850
c:\gtools\tools\testmem\testmem.cpp(42): using malloc 4567  00A56DE8
c:\gtools\tools\testmem\testmem.cpp(49): using LocalAlloc 2345  00153F68
c:\gtools\tools\testmem\testmem.cpp(62): using GlobalAlloc 3456  001548B0
c:\gtools\tools\testmem\testmem.cpp(55): using VirtualAlloc 3 MB  00260000
Allocations made in DLL module: c:\gtools\tools\testmem\memtestdll.cpp ...
c:\gtools\tools\testmem\memtestdll.cpp(58): using new BYTE[1234]  00A12110
c:\gtools\tools\testmem\memtestdll.cpp(64): using malloc 4567  00A15CF8
c:\gtools\tools\testmem\memtestdll.cpp(71): using LocalAlloc 2345  00155648
c:\gtools\tools\testmem\memtestdll.cpp(84): using GlobalAlloc 3456  00155F90
c:\gtools\tools\testmem\memtestdll.cpp(77): using VirtualAlloc 3 MB  00270000
Allocations made in STATIC module: c:\gtools\tools\testmem\memteststatic.cpp ...
c:\gtools\tools\testmem\memteststatic.cpp(19): using new BYTE[1234]  00A52D60
c:\gtools\tools\testmem\memteststatic.cpp(25): using malloc 4567  00A58000
c:\gtools\tools\testmem\memteststatic.cpp(32): using LocalAlloc 2345  00156D28
c:\gtools\tools\testmem\memteststatic.cpp(45): using GlobalAlloc 3456  00157670
c:\gtools\tools\testmem\memteststatic.cpp(38): using VirtualAlloc 3 MB  00280000

So where does this lead?

If a project includes third party libraries, linked dynamically, then using the dynamic RUNTIME (/MDd) gives a more complete report that includes any allocations made in the third party DLL. However, if a project includes third party libraries, all statically linked, the either RUNTIME, static or dynamic (/MTd or /MDd) produce the SAME report.

With regard to the 'missing' module names, for the allocations using 'new', this is only in MSVC8. It seems to suppress the module name when it would be more or less meaningless - namely when it is actually within its own crtdbg.h header file ... Both MSVC6 and MSVC7.1 will report like -

c:\program files\microsoft visual studio .net 2003\vc7\include\crtdebug.h(692), which is where the 'new' is converted to a dbg_new to include the file and line number.

Microsoft also provides a few samples, and I have included one, crt_dbg1.c, slightly modified, and containing the contents of a second sample report.c, with this source. It shows how this 'reporting' can be sent to stdout, or to a file. Attached below is the output of that sample.


top

DOWNLOAD

This zip contains the full source of this debug memory test sample
- testmem-01.zip (MD5: ed0eda58910799ac78d481975ef67c5b) -
It includes a MSVC6 build set (DSW/DSP) so can be loaded in any subsequent MSVC.


top

ALTERNATIVES

There seem NONE for Windows! I searched around for other third party 'memory checkers', but found no open source (free) examples. Unix/Linux have Valgrind, but at present there seems no WIN32 port of this. There are quite expensive products like IBM's Purity, and the BoundsChecker suite from DevPartner, and possibly others, if you are ready to fork out hard cash ;=))

SUMMARY

But all in all, I now remember more and more why in the past I have written my OWN memory reporting modules, rather than 'playing' with these rather meagre, almost meaningless scraps ... but they work well for simple cases.

Remember,
(a) if your project includes third party libraries only offered in a DLL link form, and if you want your report to include memory allocated in these, then you must use the DLL runtime (/MDd), but if it is all statically linked libraries, then the static runtime (/MTd) is also fine, and
(b) this reporting does NOT included memory allocated using LocalAlloc, GlobalAlloc nor VirtualAlloc, and memory allocated using 'new' does NOT give you the module in your code where the allocation was made, thus use good old 'malloc' throughout for the best results ;=))

Geoff.
Saturday, December 15, 2007 - December 11, 2007.


top

Contents of 'templog.txt', written by my crt_dbg1.exe ...

Running: c:\gtools\tools\testmem\debug\crt_dbg1\crt_dbg1.exe ...

Use _ASSERTE to check that the two strings are identical:
**************************************************************************
c:\gtools\tools\testmem\crt_dbg1.c(141) : Assertion failed: strcmp( p1, p2 ) == 0

Use a _RPT macro to report the string contents as a warning:
**************************************************************************
p1 points to 'This is the p1 string (34 bytes).' and
p2 points to 'This is the p2 string (34 bytes).'

Use _CRTMemDumpAllObjectsSince to check the p1 and p2 allocations:
**************************************************************************
Dumping objects ->
c:\gtools\tools\testmem\crt_dbg1.c(135) : {54} normal block at 0x003F28B0, 34 bytes long.
 Data: <This is the p2 s> 54 68 69 73 20 69 73 20 74 68 65 20 70 32 20 73
c:\gtools\tools\testmem\crt_dbg1.c(132) : {53} normal block at 0x003F2850, 34 bytes long.
 Data: <This is the p1 s> 54 68 69 73 20 69 73 20 74 68 65 20 70 31 20 73
Object dump complete.

Having freed p2, dump allocation information about p1 only:
**************************************************************************
Dumping objects ->
c:\gtools\tools\testmem\crt_dbg1.c(132) : {53} normal block at 0x003F2850, 34 bytes long.
 Data: <This is the p1 s> 54 68 69 73 20 69 73 20 74 68 65 20 70 31 20 73
Object dump complete.

Dump the changes that occurred between two memory checkpoints:
**************************************************************************
0 bytes in 0 Free Blocks.
38 bytes in 1 Normal Blocks.
0 bytes in 0 CRT Blocks.
0 bytes in 0 Ignore Blocks.
0 bytes in 0 Client Blocks.
Largest number used: 4 bytes.
Total allocations: 38 bytes.

Now the memory state at the two checkpoints is the same:
**************************************************************************

Free p1 after overwriting the end of the allocation:
**************************************************************************
HEAP CORRUPTION DETECTED: after Normal block (#53) at 0x003F2850.
CRT detected that the application wrote to memory after end of heap buffer.

Memory allocated at c:\gtools\tools\testmem\crt_dbg1.c(132).

Perform a memory check after corrupting freed memory:
**************************************************************************
HEAP CORRUPTION DETECTED: on top of Free block at 0x003F2850.
CRT detected that the application wrote to a heap buffer that was freed.

Memory allocated at c:\gtools\tools\testmem\crt_dbg1.c(189).
DAMAGED located at 0x003F2850 is 10 bytes long.

Memory allocated at c:\gtools\tools\testmem\crt_dbg1.c(189).

Using free( ) to free a Client block causes an assertion failure:
**************************************************************************
dbgheap.c(1356) : Assertion failed: pHead->nBlockUse == nBlockUse

Examine outstanding allocations (dump memory leaks):
**************************************************************************
Detected memory leaks!
Dumping objects ->
c:\gtools\tools\testmem\crt_dbg1.c(209) : {59} normal block at 0x003F2968, 10 bytes long.
 Data: <          > CD CD CD CD CD CD CD CD CD CD
Object dump complete.

Program exits without freeing a memory block:
**************************************************************************
Detected memory leaks!
Dumping objects ->
c:\gtools\tools\testmem\crt_dbg1.c(209) : {59} normal block at 0x003F2968, 10 bytes long.
 Data: <          > CD CD CD CD CD CD CD CD CD CD
Object dump complete.

EOF - memory-01.doc


top

checked by tidy  Valid HTML 4.01 Transitional