Debug new and delete Part 3

Debug new and delete Part 3

By Peter A. Pilgrim

Overload, 6(29):, December 1998


Experienced C and C++ developers are aware of the problem of allocating free store, at run-time, which is not deallocated by the time the program terminates. The problem is known as memory leakage. In order to detect the fifth case [see Overload 23 & 24] where memory is leaked by a program, there obviously has to be method of recording all calls to the global scope ::new and ::delete operators.

A practical solution for any record keeping project is to choose an appropriate data structures for the element record and a container to maintain the records. We can use classes in C++. We can place the behaviours that directly deal with elements in the record class and the behaviours that directly deal with containers in the record container class. This is a very basic object based idiom. There is no need for inheritance ( object orientation ) here.

I have chosen to implement a container for dynamic memory tracking [ Pilgrim ] that is deliberately simple and propriety. No Standard Template Library constructs are used, because the container must be available immediately at run-time without any other complicated dependencies. In particular, the container is statically declared, in the global scope namespace. Remember C++ constructs global objects (like the standard I/O objects cout , cin , and cerr ) before the main() program is called. (Yes I know that I should be using a private library namespace, but for the sake of this article I want to keep things simpler for the novice reader at least.)

But, before I describe the data record and container. The problem of conditionally compiling debugging (and linking) the eventual debuggable new and delete library remains to be solved.

It took quite a while for me to work out a reasonable compromise between the C++ preprocessor's terseness and having acceptable and readable macro definitions. I decided to intercept the allocation and deallocation of dynamic memory with two global scope namespace functions called _ register_memory() and _ unregister_memory() . Here are the macros:

#ifdef DEBUG_NEW
#define DBG_NEW(type,params) \
  (type*)_register_memory( 
       new type params , __FILE__, __LINE__ )
#define DBG_NEW_ARRAY(type,nelems) \
  (type*)_register_memory( 
       new type[nelems], __FILE__, __LINE__ )
#define DBG_DELETE(ptr) \
  delete (_unregister_memory( (ptr), 
                         __FILE__, __LINE__ ))
#define DBG_DELETE_ARRAY(ptr) \
  delete [] (_unregister_memory( (ptr), 
                         __FILE__, __LINE__ ))
#endif /*DEBUG_NEW*/

Notice that we pass the actual filename and the line number to the registration functions. This is information is essential to tracking later memory leaks.

Consider the usual way of writing these calls:

class X { 
    X() { ... }      // default constructor
    X( int aa, int bb, int cc ) { ... }
    ...
};

void f() {
  X *px = new X(1,2,3);
  X *pax = new X[100];
  delete px;
  delete [] pax;
}

Using the debuggable new and delete macros the code changes, in particular the constructor, only slightly. The idea is to avoid disturbing the casual reader with wild unfamiliar macros:

void f(){
  X *px = DBG_NEW( X, (1,2,3) );
  X *pax = DBG_NEW_ARRAY( X, 100 );
  DBG_DELETE(px);
  DBG_DELETE_ARRAY(pax);
}

Similarly when we need to switch off the debuggable new macros for production code the definitions become:

#ifndef DEBUG_NEW
#define DBG_NEW(type,params) new type params
#define DBG_NEW_ARRAY(type,nelems) \
                             new type[nelems]
#define DBG_DELETE(ptr) delete ptr
#define DBG_DELETE_ARRAY(ptr) delete [] ptr
#endif /*!DEBUG_NEW*/

How do we track the allocated pointers to blocks of memory? First, we record the important aspects of the allocation: the filename, line number, size of the block, the time for critical real-time applications, and a pointer to the prefixed header block. We must also construct, access and modify the record too. I have called this class the BaseMemory class and fore-chosen the container type by inserting next and previous pointers. The BaseMemory is a linked list node. (If this source code were written in the Java language, then the class would probably have an interface LinkedListNode.) Here is the BaseMemory class:

class BaseMemory
{
const int MagicID = 0x19FA97CE;
public:
  BaseMemory()  // Required
     : _file(), _line_num(-1),
      _memsize(0), _is_allocated(false),
      _alloc_time(0), 
      _magic_word(MagicID), 
      _prefix_header(0), 
      _prev(0), _next(0),
  {
  }
  BaseMemory( const char* filename, 
              int line_num, 
              PrefixHeader *phdr );
  BaseMemory( const char* filename, 
              int line_num, 
              PrefixHeader *phdr,
              time_t alloc_time );
  ~BaseMemory();
  BaseMemory( const BaseMemory &x  );
  
  // Assignment Operator
  BaseMemory & operator=
    ( const BaseMemory &x  );

  friend bool  operator<
    ( const BaseMemory &x, 
      const BaseMemory &y );

  // Accessor member functions
  const string & file() const 
  { return (_file); }
  const int line_num() const 
  { return (_line_num); }
  const size_t memsize() const 
  { return (_memsize); }
  const bool is_allocated() const 
  { return (_is_allocated); }
  
  // Check Base Memory
  void checkit() const
  {
  // Has the base memory been corrupted? 
  // Throw exception!
  if (_magic_word != MagicID) {
    cerr 
       << "(*DBGNEW*) base memory corrupted." 
       << endl;
    throw BaseMemoryCorrupted();
  }
}

friend class DynamicMemoryController;
    
private:
  string    _file;      // ptr to __FILE__ string
  int      _line_num;    // ptr to __LINE__ string
  size_t    _memsize;    // allocation size
  bool    _is_allocated;    // TRUE, if in use
  time_t    _alloc_time;    // alloc time
  int      _magic_word;    // Magic 
  PrefixHeader *  _prefix_header;
  BaseMemory *  _next;
  BaseMemory *  _prev;
};

The BaseMemory class has a very simple method called checkit() that checks that the object instance has not been corrupted. The method is not foolproof though. The operator< is declared for sorting base memory elements. (This member operator function is a remnant of the my aborted experiments that used a STL vector container.) BaseMemory class has a friend class, which is called DynamicMemoryController , which serves as the linked list container.

The controller for the C++ debuggable new and delete is a SINGLETON. There can only be one process and therefore only one global scope namespace new and delete manager. So what follows is a straight application of Meyerism (after Scott Meyer [ Meyers ]). The controller is presented:

class DynamicMemoryController {
// This is a SINGLETON for the whole app
private:
  DynamicMemoryController();
public:
  static DynamicMemoryController *
  get_controller()
  {
  if (the_memory_controller == 0) {
    the_memory_controller = new DynamicMemoryController();
    // Add memory dump handler
    atexit( dump_memory_leak );
   }
  return (the_memory_controller);
  }

  static bool initialised();
    
  ~DynamicMemoryController();

  void AddBaseElement( char *file, 
    int line_num, PrefixHeader *phdr );
    
  bool RemoveBaseElement( char *file, 
    int line_num, PrefixHeader *phdr );
    
  BaseMemory * FindBaseElement
    ( PrefixHeader *phdr );

  void PrintLeaks( ostream &out );
private:
  static int the_max_elements;
  static BaseMemory *  the_base_memory;
  static DynamicMemoryController *
    the_memory_controller;
};

I draw your attention to the get_controller() private method of the dynamic memory controller. This member function only ever returns one and only one single DynamicMemoryController object instance. The first time the memory controller is generated the function immediately registers the public function dump_memory_leak() with the standard C library's atexit() routine. So that when the program terminates normally or abnormally (as long it is not a core dump or general protection fault) any memory leaks are printed to the standard output.

For those of you unfamiliar with Singletons, it is a software engineering design pattern that appears in many applications. [ GoF ] A Singleton is a restricted object, which is only supposed to be created N number of times. (In our case N==1.) In C++ , the singleton object has its default constructor declared private. An alternative public class method creates the object, which is the so-called point of control. The object class DynamicMemoryController has a public declared get_controller() member function, which returns a single object, which is statically declared in the global scope.

The other problem to solve is guaranteeing the order of global object creation: making sure that the DynamicMemoryController exists before we use it in a function or procedure, or any other global initialisation in any implementation source file. To achieve this goal a helper class called Dbgnew_init is used. A static variable of type Dbgnew_init is declared as the last statement in the <dbgnew> C++ header file. The result of this last declaration guarantees the creation of the dynamic memory controller before any other that uses in a normal C++ source file. Please consult Scott Meyer's Effective C++ book for more information about the idiom.

// Filename:`dbgnew.h'
// Assumed in the private lib namespace.
class Dbgnew_init {
  // Help class to construct & destruct the 
  // `DynamicMemoryController'
  // object instance (SINGLETON)
public:
  Dbgnew_init();
  ~Dbgnew_init();
    
private:
  Dbgnew_init( const Dbgnew_init & );
  Dbgnew_init & operator=( const Dbgnew_init & );
  static unsigned short _count;  
  // The number of `Dbgnew_init'
  // objects currently in existance
};
...
static Dbgnew_init  dbgnew_init;

// static object created for
// source code translation unit
// Filename:`dbgnew.cc'
...
unsigned short Dbgnew_init::_count;

Dbgnew_init::Dbgnew_init()
{
  if ( _count++ == 0 ) {
  // Force the construction of the dynamically
  // allocated `DynamicMemoryController' 
  // singleton. We throw away the pointer.
  DynamicMemoryController::get_controller();
  }
}

Dbgnew_init::~Dbgnew_init()
{
  if ( --_count == 0 ) {
  // Force the destruction of the 
  // DynamicMemoryController' singleton. 
  // We use the returned pointer to delete the
  // instance.
  delete
    DynamicMemoryController::get_controller();
  }
}

The static global scope variables in the implementation file are declared like this:

unsigned short Dbgnew_init::_count;
  // The dynamic memory controller's 
  // helper class's counter variable

DynamicMemoryController* 
  DynamicMemoryController::
  the_memory_controller;
// Pointer to the dynamic memory controller 
// singleton object instance
BaseMemory*   
  DynamicMemoryController::
  the_base_memory;
// Pointer to the controller's base memory

int 
  DynamicMemoryController::
  the_max_elements;

The registration functions are easy to illustrate. You can see the internal workings of the C++ preprocessor macros that I presented at the top of this article. In order to register a generic pointer to a prefix header pointer to a recognised memory block requires, first, casting from void* back to the PrefixHeader types. Secondly the prefix header pointer is recorded by adding it to the linked list maintained by to the container DynamicMemoryController . A member function called AddBaseElement() performs this function and automatically generates the BaseMemory object instance (without using new() as will be explained in the next article).

void *_register_memory
  (void *input_ptr, char *file, int line_num )
{
  // Record this allocation
  PrefixHeader *phdr =
    ((PrefixHeader*)input_ptr) - 1;
  DynamicMemoryController::get_controller()
    ->AddBaseElement( file, line_num, phdr );
  return (input_ptr);
}

Similarly the unregistration of pointers follows first a similar recasting to PrefixHeader types. However, the prefix header and it's corresponding BaseMemory record is removed from the linked list of the controller by the member function RemoveBaseElement() .

void *_unregister_memory
  (void *input_ptr, char *file, int line_num )
{
  // Record this deallocation
  PrefixHeader *phdr = 
    ((PrefixHeader*)input_ptr) - 1;
  diagnose_memory( phdr, file, line_num );
  DynamicMemoryController::get_controller()
    ->RemoveBaseElement(file,line_num,phdr );
  return (input_ptr);
}

The function dump_memory_leak() is very simple. For purposes of the article it's report is dumped to the standard output, but ideally the function would create a specially named text output file and write the report there.

extern "C" void  dump_memory_leak( )
{
  // Print out memory leaks
  DynamicMemoryController::get_controller()
    ->PrintLeaks( cout );
}

In the final article I will demonstrate how I created the linked list of base memory without using hundreds of calls to the global new operator. I will also describe in further detail the member functions of the DynamicMemoryController class and it's BaseMemory class.

Bibliography

[Pilgrim] "Dynamic Memory Integrity", ACCU/ C Vu 8.5 , and "Dynamic Memory Tracking", ACCU/ C Vu 8.6 , Peter A. Pilgrim

[Meyers] " Effective C++ ", Scott Meyers, Addison Wesley 1995.

[GoF] " Design Patterns ", Erich Gamma, Helm et al, Addison Wesley.






Your Privacy

By clicking "Accept Non-Essential Cookies" you agree ACCU can store non-essential cookies on your device and disclose information in accordance with our Privacy Policy and Cookie Policy.

Current Setting: Non-Essential Cookies REJECTED


By clicking "Include Third Party Content" you agree ACCU can forward your IP address to third-party sites (such as YouTube) to enhance the information presented on this site, and that third-party sites may store cookies on your device.

Current Setting: Third Party Content EXCLUDED



Settings can be changed at any time from the Cookie Policy page.