- •Introduction
- •Saving Time with This Book
- •Conventions Used in This Book
- •Part II: Working with the Pre-Processor
- •Part III: Types
- •Part IV: Classes
- •Part V: Arrays and Templates
- •Part VI: Input and Output
- •Part VII: Using the Built-in Functionality
- •Part VIII: Utilities
- •Part IX: Debugging C++ Applications
- •Part X: The Scary (or Fun!) Stuff
- •Icons Used in This Book
- •Creating and Implementing an Encapsulated Class
- •Creating a Mailing-List Application
- •Testing the Mailing-List Application
- •Customizing a Class with Polymorphism
- •Testing the Virtual Function Code
- •Why Do the Destructors Work?
- •Delayed Construction
- •The cDate Class
- •Testing the cDate Class
- •Creating the Header File
- •Testing the Header File
- •The Assert Problem
- •Fixing the Assert Problem
- •Using the const Construct
- •Identifying the Errors
- •Fixing the Errors
- •Fixing What Went Wrong with the Macro
- •Using Macros Appropriately
- •Using the sizeof Function
- •Evaluating the Results
- •Using sizeof with Pointers
- •Implementing the Range Class
- •Testing the Range Class
- •Creating the Matrix Class
- •Matrix Operations
- •Multiplying a Matrix by a Scalar Value
- •Multiplying a Matrix by Scalar Values, Take 2
- •Testing the Matrix Class
- •Implementing the Enumeration Class
- •Testing the Enumeration Class
- •Implementing Structures
- •Interpreting the Output
- •Defining Constants
- •Testing the Constant Application
- •Using the const Keyword
- •Illustrating Scope
- •Interpreting the Output
- •Using Casts
- •Addressing the Compiler Problems
- •Testing the Changes
- •Implementing Member-Function Pointers
- •Updating Your Code with Member-Function Pointers
- •Testing the Member Pointer Code
- •Customizing Functions We Wrote Ourselves
- •Testing the Default Code
- •Fixing the Problem
- •Testing the Complete Class
- •Implementing Virtual Inheritance
- •Correcting the Code
- •Rules for Creating Overloaded Operators
- •Using Conversion Operators
- •Using Overloaded Operators
- •Testing the MyString Class
- •Rules for Implementing new and delete Handlers
- •Overloading new and delete Handlers
- •Testing the Memory Allocation Tracker
- •Implementing Properties
- •Testing the Property Class
- •Implementing Data Validation with Classes
- •Testing Your SSN Validator Class
- •Creating the Date Class
- •Testing the Date Class
- •Some Final Thoughts on the Date Class
- •Creating a Factory Class
- •Testing the Factory
- •Enhancing the Manager Class
- •Implementing Mix-In Classes
- •Testing the Template Classes
- •Implementing Function Templates
- •Creating Method Templates
- •Using the Vector Class
- •Creating the String Array Class
- •Working with Vector Algorithms
- •Creating an Array of Heterogeneous Objects
- •Creating the Column Class
- •Creating the Row Class
- •Creating the Spreadsheet Class
- •Testing Your Spreadsheet
- •Working with Streams
- •Testing the File-Reading Code
- •Creating the Test File
- •Reading Delimited Files
- •Testing the Code
- •Creating the XML Writer
- •Testing the XML Writer
- •Creating the Configuration-File Class
- •Setting Up Your Test File
- •Building the Language Files
- •Creating an Input Text File
- •Reading the International File
- •Testing the String Reader
- •Creating a Translator Class
- •Testing the Translator Class
- •Creating a Virtual File Class
- •Testing the Virtual File Class
- •Using the auto_ptr Class
- •Creating a Memory Safe Buffer Class
- •Throwing and Logging Exceptions
- •Dealing with Unhandled Exceptions
- •Re-throwing Exceptions
- •Creating the Wildcard Matching Class
- •Testing the Wildcard Matching Class
- •Creating the URL Codec Class
- •Testing the URL Codec Class
- •Testing the Rot13 Algorithm
- •Testing the XOR Algorithm
- •Implementing the transform Function to Convert Strings
- •Testing the String Conversions
- •Implementing the Serialization Interface
- •Creating the Buffer Class
- •Testing the Buffer Class
- •Creating the Multiple-Search-Path Class
- •Testing the Multiple-Search-Path Class
- •Testing the Flow Trace System
- •The assert Macro
- •Logging
- •Testing the Logger Class
- •Design by Contract
- •Adding Logging to the Application
- •Making Functions Inline
- •Avoiding Temporary Objects
- •Passing Objects by Reference
- •Choosing Initialization Instead of Assignment
- •Learning How Code Operates
- •Testing the Properties Class
- •Creating the Locking Mechanism
- •Testing the Locking Mechanism
- •Testing the File-Guardian Class
- •Implementing the Complex Class
- •Creating the Conversion Code
- •Testing the Conversion Code
- •A Sample Program
- •Componentizing
- •Restructuring
- •Specialization
- •Index
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.