Выбрать главу

      printf("Leaked memory at:\n");

      for (size_t i = 0; i < nptrs; ++i)

        printf("\t%p (file: %s, line %ld)\n",

          memMap[i].ptr, memMap[i].file, memMap[i].line);

    }

    else

      printf("No user memory leaks!\n");

  }

};

// Static dummy object

Sentinel s;

} // End anonymous namespace

// Overload scalar new

void* operator new(size_t siz, const char* file,

  long line) {

  void* p = malloc(siz);

  if (activeFlag) {

    if (nptrs == MAXPTRS) {

      printf("memory map too small (increase MAXPTRS)\n");

      exit(1);

    }

    memMap[nptrs].ptr = p;

    memMap[nptrs].file = file;

    memMap[nptrs].line = line;

    ++nptrs;

  }

  if (traceFlag) {

    printf("Allocated %u bytes at address %p ", siz, p);

    printf("(file: %s, line: %ld)\n", file, line);

  }

  return p;

}

// Overload array new

void* operator new[](size_t siz, const char* file,

  long line) {

  return operator new(siz, file, line);

}

// Override scalar delete

void operator delete(void* p) {

  if (findPtr(p) >= 0) {

    free(p);

    assert(nptrs > 0);

    delPtr(p);

    if (traceFlag)

      printf("Deleted memory at address %p\n", p);

  }

  else if (!p && activeFlag)

    printf("Attempt to delete unknown pointer: %p\n", p);

}

// Override array delete

void operator delete[](void* p) {

  operator delete(p);

} ///:~

The Boolean flags traceFlag and activeFlag are global, so they can be modified in your code by the macros TRACE_ON( ), TRACE_OFF( ), MEM_ON( ), and MEM_OFF( ). In general, enclose all the code in your main( ) within a MEM_ON( )-MEM_OFF( ) pair so that memory is always tracked. Tracing, which echoes the activity of the replacement functions for operator new( ) and operator delete( ), is on by default, but you can turn it off with TRACE_OFF( ). In any case, the final results are always printed (see the test runs later in this chapter).

The MemCheck facility tracks memory by keeping all addresses allocated by operator new( ) in an array of Info structures, which also holds the file name and line number where the call to new occurred. As much information as possible is kept inside the anonymous namespace so as not to collide with any names you might have placed in the global namespace. The Sentinel class exists solely to have a static object’s destructor called as the program shuts down. This destructor inspects memMap to see if any pointers are waiting to be deleted (in which case you have a memory leak).

Our operator new( ) uses malloc( ) to get memory, and then adds the pointer and its associated file information to memMap. The operator delete( ) function undoes all that work by calling free( ) and decrementing nptrs, but first it checks to see if the pointer in question is in the map in the first place. If it isn’t, either you’re trying to delete an address that isn’t on the free store, or you’re trying to delete one that’s already been deleted and therefore previously removed from the map. The activeFlag variable is important here because we don’t want to process any deallocations from any system shutdown activity. By calling MEM_OFF( ) at the end of your code, activeFlag will be set to false, and such subsequent calls to delete will be ignored. (Of course, that’s bad in a real program, but as we said earlier, our purpose here is to find your leaks; we’re not debugging the library.) For simplicity, we forward all work for array new and delete to their scalar counterparts.

The following is a simple test using the MemCheck facility.

//: C02:MemTest.cpp

//{L} MemCheck

// Test of MemCheck system

#include <iostream>

#include <vector>

#include <cstring>

#include "MemCheck.h"   // Must appear last!

using namespace std;

class Foo {

  char* s;

public:

  Foo(const char*s ) {

    this->s = new char[strlen(s) + 1];

    strcpy(this->s, s);

  }

  ~Foo() {

    delete [] s;

  }

};

int main() {

  MEM_ON();

  cout << "hello\n";

  int* p = new int;

  delete p;

  int* q = new int[3];

  delete [] q;

  int* r;

  delete r;

  vector<int> v;

  v.push_back(1);

  Foo s("goodbye");

  MEM_OFF();

} ///:~

This example verifies that you can use MemCheck in the presence of streams, standard containers, and classes that allocate memory in constructors. The pointers p and q are allocated and deallocated without any problem, but r is not a valid heap pointer, so the output indicates the error as an attempt to delete an unknown pointer.

hello

Allocated 4 bytes at address 0xa010778 (file: memtest.cpp, line: 25)

Deleted memory at address 0xa010778

Allocated 12 bytes at address 0xa010778 (file: memtest.cpp, line: 27)

Deleted memory at address 0xa010778

Attempt to delete unknown pointer: 0x1

Allocated 8 bytes at address 0xa0108c0 (file: memtest.cpp, line: 14)

Deleted memory at address 0xa0108c0

No user memory leaks!

Because of the call to MEM_OFF( ), no subsequent calls to operator delete( ) by vector or ostream are processed. You still might get some calls to delete from reallocations performed by the containers.

If you call TRACE_OFF( ) at the beginning of the program, the output is as follows:

hello

Attempt to delete unknown pointer: 0x1

No user memory leaks!.

Summary

Much of the headache of software engineering can be avoided by being deliberate about what you’re doing. You’ve probably been using mental assertions as you’ve crafted your loops and functions anyway, even if you haven’t routinely used the assert( ) macro. If you’ll use assert( ), you’ll find logic errors sooner and end up with more readable code as well. Remember to only use assertions for invariants, though, and not for runtime error handling.