Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ Timesaving Techniques (2005) [eng].pdf
Скачиваний:
65
Добавлен:
16.08.2013
Размер:
8.35 Mб
Скачать

Overloading new and delete Handlers

129

This code contains a subtle, but important, memory leak. If you call the function with a value such as 102 passed for x, you will see the problem. The function allocates a block of memory that is 200 bytes long (at 1), and then returns without de-allocating the block (at 2). That memory is then consumed until the program exits, and is no longer available to the application. This might not seem like such a big problem — unless this routine is called several thousand times. Suddenly that 200-byte block becomes a several-megabyte memory leak. Not a good outcome at all.

Fortunately, the designers of C++ considered that problems like this could easily crop up. Although they chose not to build in a garbage-collection system, as in Java, they did provide the building blocks for creating your own memory allocation and deallocation system, and keeping track of such things. To keep track of memory allocation in C++, we need the ability to overload the new and delete handlers in the language. You might think that this would be a complicated affair, but as Technique 23 shows, overloading operators (so they appear to be a basic part of the language) is simple in C++. The new and delete operators are no exception to the overloading process, although you have to go about it a little differently. In this technique, we look at how you can overload the new and delete handlers for the entire system, although the same process can be scaled down to just the class level.

Rules for Implementing new and delete Handlers

There are a few things to note when you are implementing your own new and delete handlers in your application code.

You may not call new and delete within your new or delete handlers. This might seem obvious, but issuing those calls is almost automatic for

some developers. In short, new and delete may not be called recursively.

You may not call any methods, functions, or objects that call new or delete within your handlers. If you call a function within a new handler that calls new, you get an instantly recursive call. Following this rule is often harder than it looks. (For example, you cannot use the STL containers because they allocate memory.)

Your new and delete handlers must be very fast. Their code is often called over and over, and must not slow down the application they are being used from.

You cannot change the process in which the new and delete operators are called. That is, you can’t return a smaller or larger block than was asked for to the application. Doing so can break many programs.

Overloading new and delete Handlers

With these rules in mind, how can we overload the new and delete operators to keep track of what is being allocated in a given program and report on which allocations were never freed? Let’s take a look at an example of that right now.

1. In the code editor of your choice, create a new file to hold the code for the implementation of the source file.

In this example, the file is named ch24.cpp, although you can use whatever you choose.

2. Type the code from Listing 24-1 into your file.

Better yet, copy the code from the source file on this book’s companion Web site.

130 Technique 24: Defining Your Own new and delete Handlers

LISTING 24-1: NEW AND DELETE HANDLERS

#include <stdio.h> #include <stdlib.h>

typedef struct {

long

number;

long

address;

long

size;

char

file[64];

long

line;

} lALLOC_INFO;

lALLOC_INFO

*allocations[ 100000 ];

int

nPos = 0;

void AddTrack(long addr, long asize)

{

if ( asize == 2688 ) printf(“Found one!\n”);

lALLOC_INFO *info = (lALLOC_INFO *)malloc(sizeof(lALLOC_INFO)); info->address = addr;

info->size = asize; info->number = nPos; allocations[nPos] = info; nPos ++;

};

bool RemoveTrack(long addr)

{

bool bFound = false;

for(int i = 0; i != nPos; i++)

{

if(allocations[i]->address == addr)

{

// Okay, delete this one. free( allocations[i] ); bFound = true;

// And copy the rest down to it. for ( int j=i; j<nPos-1; ++j )

allocations[j] = allocations[j+1]; nPos --;

break;

}

}

if ( !bFound )

printf(“Unable to find allocation for delete [%ld]\n”,addr); return bFound;

};

 

 

Overloading new and delete Handlers

131

This code keeps track of all allocations — and

or the size of a given object. This code could eas-

adds or removes them from a global array as we

ily be moved to a separate utility file but we will

do our processing. This way, we can track all

include it in the same file for simplicity.

 

calls to new or delete within our application —

This code simply steps through the list of alloca-

and report on the calls to new that are not

tions we have kept track of in the add and remove

matched with a call to delete. Of course, this

routines and reports on anything it finds that

approach limits how many allocations we are

was not yet freed. This doesn’t necessarily mean

going to track (it’s a large, but not infinite, num-

that the allocation is a leak, though, as we will

ber). We can’t dynamically allocate this array

see. What it means is that at the moment of this

without writing a bunch of fancy code that’s

particular memory-state snapshot, the alloca-

beyond the scope of this example, so we will use

tions in the list have not been freed up.

 

this compromise for now.

 

 

 

4. Add the code from Listing 24-3 to your source

3. Add the code from Listing 24-2 to your source

file below the remaining code.

 

file.

 

 

 

 

 

This code generates a report of what blocks are

This implements the actual new and delete

methods. Once again, we could easily move

currently allocated and how big they are. This

these to a separate utility file, but it is easier to

aids the developer in tracking down the offending

leave it in one place. This functionality is added

code that created the memory leak in the first

separately to indicate how you would append

place. Of course, knowing where the leak

this code to an existing program.

 

occurred doesn’t help if the leak happens in a low-

 

 

 

level library (because we have no access to the

This will implement the actual overload of the

library source code and couldn’t modify it if we

new and delete operators. To implement that

did), but at least the size will help some. By know-

operation, add the code in Listing 24-3 to your

ing the size of our allocation, we might be able to

source file.

 

map that to a specific block size in the program,

 

 

LISTING 24-2: ALLOCATION REPORT

 

 

 

 

 

 

 

void DumpUnfreed()

 

 

 

{

 

 

 

long totalSize = 0;

 

 

 

printf(“--------------------

Allocations ----------------------

\n”);

 

for(int i = 0; i < nPos; i++)

 

 

 

{

 

 

 

lALLOC_INFO *pInfo = allocations[i];

 

 

printf(“(%ld) ADDRESS %x\t Size: %d unfreed\n”,

 

pInfo->number, pInfo->address, pInfo->size);

 

totalSize += pInfo->size;

 

 

 

}

 

 

 

printf(“------------------------------------------------------

 

\n”);

 

printf(“Total Unfreed: %d bytes\n\n\n”, totalSize);

};

132 Technique 24: Defining Your Own new and delete Handlers

LISTING 24-3: OVERLOADED NEW AND DELETE HANDLERS

inline void * __cdecl operator new(unsigned int size)

{

printf(“Basic operator new called\n”); void *ptr = (void *)malloc(size); AddTrack((long)ptr, size); return(ptr);

};

inline void * __cdecl operator new[](unsigned int size)

{

printf(“Array operator new called\n”); void *ptr = (void *)malloc(size); AddTrack((long)ptr, size); return(ptr);

};

inline void __cdecl operator delete(void *p)

{

printf(“Basic operator delete called\n”); if ( RemoveTrack((long)p) )

free(p);

};

inline void __cdecl operator delete[](void *p)

{

printf(“Array operator delete called\n”); if ( RemoveTrack((long)p) )

free(p);

};

5. Save the source-code file.

These implementations of the code are nothing special. We simply allocate memory (using the built-in C function malloc) and de-allocate the memory by using the free function. The code includes some debugging printf statements that allow you to show which functions are being called at what time. Within each allocation or de-allocation operator, we call the appropriate tracking function to add or remove this particular allocation from the global array. One thing to note is that this code is actually better than the standard C++ implementation, because it verifies that a given pointer was allocated before it allows it to be freed. You could (for example) cause some mayhem if you were to do this:

char *c = new c[100];

//Do some stuff delete c;

//Do some more stuff delete c;

Expect bad things to happen in a case like this: It’s deleting the same pointer twice, which tends to corrupt the stack and destroy all memory in the system. In our system, however, this process is caught and an error message is displayed. Furthermore, the actual pointer is not deleted a second time — so there’s no memory corruption in the system and your program does not crash. That’s a good enough reason to use a system like this in your production code.