In the following file, which contains the memory tracking implementation, everything is done with C standard I/O rather than with C++ iostreams. It shouldn’t make a difference, really, since we’re not interfering with iostreams’ use of the free store, but it’s safer to not take a chance. (Besides, we tried it. Some compilers complained, but all compilers were happy with the version.) .
//: C02:MemCheck.cpp {O}
#include
#include
#include
using namespace std;
#undef new
// Global flags set by macros in MemCheck.h
bool traceFlag = true;
bool activeFlag = false;
namespace {
// Memory map entry type
struct Info {
void* ptr;
const char* file;
long line;
};
// Memory map data
const size_t MAXPTRS = 10000u;
Info memMap[MAXPTRS];
size_t nptrs = 0;
// Searches the map for an address
int findPtr(void* p) {
for (int i = 0; i < nptrs; ++i)
if (memMap[i].ptr == p)
return i;
return -1;
}
void delPtr(void* p) {
int pos = findPtr(p);
assert(p >= 0);
// Remove pointer from map
for (size_t i = pos; i < nptrs-1; ++i)
memMap[i] = memMap[i+1];
--nptrs;
}
// Dummy type for static destructor
struct Sentinel {
~Sentinel() {
if (nptrs > 0) {
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 traceFlagand activeFlagare 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 MemCheckfacility tracks memory by keeping all addresses allocated by operator new( )in an array of Infostructures, which also holds the file name and line number where the call to newoccurred. 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 Sentinelclass exists solely to have a static object’s destructor called as the program shuts down. This destructor inspects memMapto 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 activeFlagvariable 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, activeFlagwill be set to false, and such subsequent calls to deletewill 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 newand deleteto their scalar counterparts .
The following is a simple test using the MemCheckfacility.
//: C02:MemTest.cpp
//{L} MemCheck
// Test of MemCheck system
#include
#include
#include
#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 v;
v.push_back(1);
Foo s("goodbye");
MEM_OFF();
} ///:~
This example verifies that you can use MemCheckin the presence of streams, standard containers, and classes that allocate memory in constructors. The pointers pand qare allocated and deallocated without any problem, but ris 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 vectoror ostreamare processed. You still might get some calls to deletefrom 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! .
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.
Читать дальше