The C++ Debugging Support Library

Tutorial 6: The debugging of dynamic memory allocations

Introduction

For an introduction please read chapter Memory Allocation Debug Support: Introduction of the Reference Manual.

Conventions

This tutorial will give a quick overview of the conventions that you have to follow to fully benefit from the support of libcwd for the debugging of dynamic memory allocations.

Environment

First make sure that support is compiled in.  Check the headerfile libcwd/config.h and make sure that the macro CWDEBUG_ALLOC is set to 1.  If it is not defined, then you'll have to reconfigure, recompile and install libcwd.  Use ./configure --enable-alloc during configure.

You also want the macros CWDEBUG_LOCATION and CWDEBUG_MAGIC to be defined to 1.

Header files

There is no special header file needed.  You will need to include "debug.h" in the same way as is described in tutorial 5 and everything needed will be included from libcwd/debug.h.  In the remainder of this tutorial we will simple include libcwd/debug.h directly and not create any custom debug channels.

The Allocated memory Overview

Libcwd does some basic checks on de-allocations and memory leaks (at the end of the program), but there is nothing to teach about that: you'll see what happens when you make a mistake.

However, at any moment in your program you can ask libcwd to print an overview of all memory allocations to a debug object (to channel dc::malloc) or to an arbitrary ostream.  All details of the Allocated memory Overview are described in chapter Overview Of Allocated Memory of the Reference Manual.

The following example program writes the Allocated memory Overview to the default debug object libcw_do:

Compile as: g++ -DCWDEBUG amo.cc -lcwd -o amo

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

The output of this program is very simple,

MALLOC  : Allocated memory: 0 bytes in 0 blocks.

because we didn't allocate any memory.

Now let us actually allocate some memory:

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );

  int* p = new int [100];

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

Also the call to operator new[] is written to debug channel MALLOC:

MALLOC  : operator new[] (size = 400) = 0x804f310
MALLOC  : Allocated memory: 400 bytes in 1 blocks.
new[]     0x804f310             <unknown type>; (sz = 400) 

The call to list_allocations_on() is responsible for the last two lines.

There is something missing however!  When we use CWDEBUG_LOCATION we expect source file and line number information of every memory allocation, and there is none.  In order to find out what is wrong, we also turn on debug channel dc::bfd:

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );
  Debug( dc::bfd.on() );

  int* p = new int [100];

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

Which results in the following output:

MALLOC  : operator new[] (size = 400) = <unfinished>
BFD     :     Loading debug symbols from /home/carlo/c++/libcw/www/tutorial/amo... done (153 symbols)
BFD     :     Loading debug symbols from /usr/local/lib/libcwd.so.0 (0x4001a000) ... done (1529 symbols)
BFD     :     Loading debug symbols from /usr/lib/libstdc++-libc6.1-2.so.3 (0x4006b000) ... done (1578 symbols)
BFD     :     Loading debug symbols from /lib/libm.so.6 (0x400b2000) ... done (1295 symbols)
BFD     :     Loading debug symbols from /lib/libc.so.6 (0x400cf000) ... done (4025 symbols)
BFD     :     Loading debug symbols from /usr/lib/libbfd-2.10.0.18.so (0x401c5000) ... done (1191 symbols)
BFD     :     Loading debug symbols from /lib/libdl.so.2 (0x4021c000) ... done (88 symbols)
BFD     :     Loading debug symbols from /lib/ld-linux.so.2 (0x40000000) ... done (293 symbols)
BFD     :     Warning: Address 0x804aa8e in section .text does not have a line number, perhaps the unit containing the function
              `main' wasn't compiled with flag -g
MALLOC  : <continued> 0x804e898
MALLOC  : Allocated memory: 400 bytes in 1 blocks.
new[]     0x804e898             <unknown type>; (sz = 400) 

Please note the following

And indeed, we forgot to compile amo.cc with -g

When we compile correctly, using g++ -g -DCWDEBUG amo.cc -lcwd -o amo, the output of the program becomes:

MALLOC  : operator new[] (size = 400) = 0x804f208
MALLOC  : Allocated memory: 400 bytes in 1 blocks.
new[]     0x804f208               amo.cc:9    <unknown type>; (sz = 400) 

As you can see, the type of the object for which the memory was allocated is still unknown.  You can make the Allocated memory Overview better surveyable by adding a «tag» for every allocation that your program is doing:

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );

  int* p = new int [100];
  AllocTag(p, "A test array");

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

This results in the output

MALLOC  : operator new[] (size = 400) = 0x804f7a8
MALLOC  : Allocated memory: 400 bytes in 1 blocks.
new[]     0x804f7a8               amo.cc:9    int[100]; (sz = 400)  A test array

The second parameter of AllocTag() may be anything that can be written to an ostream (like is the case for Dout()).  However, it is only processed once: the first time it is called.  This allows to even add an AllocTag() at places where a low cpu usage is important and/or in loops that allocate a very large number of memory blocks (the comment is only stored once).

Consider the following code:

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );

  int* p[4];
  for(int i = 0; i < 4; ++i)
  {
    p[i] = new int [100];
    AllocTag(p[i], "Test array number " << i);	// This won't work
  }

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

The Allocated memory Overview will show four times the same tag:

MALLOC  : operator new[] (size = 400) = 0x804f8d0
MALLOC  : operator new[] (size = 400) = 0x8137c80
MALLOC  : operator new[] (size = 400) = 0x8138028
MALLOC  : operator new[] (size = 400) = 0x81383d0
MALLOC  : Allocated memory: 1600 bytes in 4 blocks.
new[]     0x81383d0               amo.cc:12   int[100]; (sz = 400)  Test array number 0
new[]     0x8138028               amo.cc:12   int[100]; (sz = 400)  Test array number 0
new[]     0x8137c80               amo.cc:12   int[100]; (sz = 400)  Test array number 0
new[]     0x804f8d0               amo.cc:12   int[100]; (sz = 400)  Test array number 0

If you don't care about the extra memory and cpu usage, you can also use AllocTag_dynamic_description(), which will work.

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );

  int* p[4];
  for(int i = 0; i < 4; ++i)
  {
    p[i] = new int [100];
    AllocTag_dynamic_description(p[i], "Test array number " << i);	// This will work
  }

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

gives as result

MALLOC  : operator new[] (size = 400) = 0x804f968
MALLOC  : operator new[] (size = 400) = 0x8137d70
MALLOC  : operator new[] (size = 400) = 0x8138290
MALLOC  : operator new[] (size = 400) = 0x804f6f8
MALLOC  : Allocated memory: 1600 bytes in 4 blocks.
new[]     0x804f6f8               amo.cc:12   int[100]; (sz = 400)  Test array number 3
new[]     0x8138290               amo.cc:12   int[100]; (sz = 400)  Test array number 2
new[]     0x8137d70               amo.cc:12   int[100]; (sz = 400)  Test array number 1
new[]     0x804f968               amo.cc:12   int[100]; (sz = 400)  Test array number 0

Often just the type of an object tells you enough about what it is.  In that case you can omit the comment completely and simply use AllocTag1(p).  Or, if you don't need any operator<<, you can use AllocTag2(p, "Some string constant here").  Finally, you can use the macro NEW to catch the type of an allocation as if you did a new followed by a AllocTag1(p):

#include "sys.h"		// See tutorial 2.
#include "debug.h"

int main(void)
{
  Debug( libcw_do.on() );
  Debug( dc::malloc.on() );

  int* p = NEW( int [100] );

  Debug( list_allocations_on(libcw_do) );

  return 0;
}

would output

MALLOC  : operator new[] (size = 400) = 0x804f4f0
MALLOC  : Allocated memory: 400 bytes in 1 blocks.
new[]     0x804f4f0               amo.cc:9    int[100]; (sz = 400) 

Copyright © 2001, 2002 Carlo Wood.  All rights reserved.