KPMG Peat Marwick • Strategic Services
Does your application have any memory leaks? Would you like to find out? This article introduces a technique to find all the TObject descendants which an application has allocated but not freed. It includes MacApp 3.0 and 3.1 classes to do the work with almost no changes to an existing application, no changes to operator new or operator delete, and no changes to MacApp itself. The technique supplements other methods, such as the use of Apple’s “Leaks” dcmd.
What You’ll Need
In order to use the accompanying source code you’ll need MacApp 3.0.1 with Steve Jasik’s “The Debugger” or MacApp 3.1 (I used 3.1b1) with either The Debugger or Mike Lockwood’s “VoodooMonkey” debugger. VoodooMonkey is available on Apple’s “Tool Chest” edition of its monthly developer CD.
I first started using Steve Jasik’s “The Debugger” when I learned about its “Objects By Time” display. This features shows all the objects in the heap in order of creation. One thing I hoped to do was to place a breakpoint before the last instruction in my application and see what objects were left. My theory was that these objects would represent memory leaks in my application.
My theory had a flaw. MacApp does not clean up after itself. It frees some of the objects that it creates but not others. There doesn’t seem to be any reason for the disparity; I expect the MacApp team didn’t have a policy on the matter.
In MacApp 3.0.1 there are 25 or 30 objects that MacApp never frees. The breakpoint I placed before the last instruction was still useful; I could see some objects which were not instances of MacApp classes, and I could tell from the position of some objects that were that they had been allocated by me, not by MacApp. I was not satisfied; having a compulsive nature I yearned for something pure. I decided to see how easy it would be to clean up after MacApp.
Should one free memory when quitting?
When a Mac application terminates its heap zone is destroyed. Why should the application bother to free memory it has allocated within that zone? Haven’t we all seen applications that seem to take forever to quit for no apparent reason?
My answer is that one may want to explicitly free memory at termination for one of two reasons. First, one may be debugging and want to have a look at what’s left in the heap after “known” allocations have been freed. Second, one may want to take into account memory that is not allocated within an application’s heap. Today that could be MultiFinder memory; tomorrow it might be the result of some new memory allocation scheme.
I don’t want my application to be slower than it has to be solely to accommodate my fears for the future. I use a compile time variable, qCleanUpAtQuit, that tells my code whether to bother to free RAM when the application terminates. I usually turn it on for development and off for beta test and shipping versions.
A typical application will check qCleanUpAtQuit in an override of TApplication::Free(). If qCleanUpAtQuit is true then the application will delete objects that span the lifetime of the application, such as server or prototype objects allocated by the IMyApplication method. One must be careful, though, to always call a Free() method that has other effects besides freeing RAM, for instance a Free() method that closes files.
Cleaning up after MacApp
How to use TTidyApplication
In order to clean up after MacApp I wrote a small class which I called TTidyApplication. It’s a “drop in” replacement for TApplication. To use it one substitutes
class TMyApplication: public TTidyApplication
class TMyApplication: public TApplication
If one were worried about having an extra class around one could condition the substitution using qCleanUpAtQuit.
If you’re using MacApp 3.1 you’ll want to adjust the various Class macros as well.
What the TTidyApplication code looks like
The TTidyApplication interface is straightforward:
class TTidyApplication: public TApplication
virtual pascal void Free(); // Override
TTidyApplication::Free() doesn’t have much to hide either. DeInitUMacApp() is a local function. LastChance() is an almost empty local function; it’s a convenient place to set a breakpoint to check and see whether any objects are left on the heap.
pascal void TTidyApplication::Free() // Override
I could have put in a long list of calls to FreeIfObject() for every MacApp object my debugger had told me was still allocated. Instead I tried to parallel the structure which initializes these objects. For each InitU… function I wrote a corresponding DeInitU… function. The DeInitU… function Frees each allocation made by the corresponding InitU… function, but in reverse order. I worked in reverse order in case any dependencies existed among the allocated objects.
I implemented the class this way because I found it easier to keep track of what I’d done. The structure stood me in good stead when I updated TTidyApplication to work with MacApp 3.1; the update was straightforward.
Why a Class?
The first version of my code consisted of a set of functions, not a new class. I decided to turn the functionality into a class for several reasons. First, I felt I really was overriding TApplication::Free() in the sense that I was providing functionality that could or should have been there all along. Second, using methods made it easier to refer to fields of TApplication. Third, this approach made the functionality easy to find.
I have used the idea of an intermediate application class in other contexts, for instance to add support for a database to MacApp. I’ve found it’s a good way to factor functionality.
The Free() method for an application which descends from TTidyApplication will look like this:
void TMyApplication::Free() // Override
// Clean up objects whose Free methods have side
// effects other than Freeing RAM
gDatabase->Free(); // For example
// Clean up objects just to get them out of the way.
// It’s OK to do this only when debugging!
gWidgetServer->Free() // For example
inherited ::Free(); // Or Inherited (MA3.1)
// or TTidyApplication::Free() if you prefer!
Dealing with Private Classes
It would have been be nice if MacApp’s InitU… methods had corresponding DeInitU… methods. Since there are none I had to write them. It’s a good thing Apple provides the source code for MacApp!
Some of MacApp’s methods use private classes, that is, classes declared only in a .cp file. These private classes are all TObject descendants; one can arrange to call their Free() methods so long as one is careful.
This will work:
class PascalObj TMenuTable;
extern TMenuTable* gMenuTable;
gMenuTable = (TMenuTable *) FreeIfObject((TObject *) gMenuTable);
extern TObject * gMenuTable;
gMenuTable = FreeIfObject(gMenuTable);
but this is dangerous:
extern TObject * gMenuTable;
The compiler may decide that it can optimize this last construction into a simple procedure call. This would not be good; the method must be dispatched polymorphically so that the appropriate override of Free is called.
Fighting with MacApp
One MacApp class, TDeskScrapView, was never intended to be deallocated. It overrides Free() and does not call inherited::Free(). C++ provides a way around this:
TView * theDeskScrapView = gClipboardMgr->fClipOrphanage;
if (theDeskScrapView != NULL)
Getting along with MacApp
The comment for TApplication::fDone in the MacApp source code says “set this to TRUE when you want the application to terminate.” Don’t do it! MacApp prefers that you post a TQuitCommand. If you don’t then TApplication::Close() won’t be called. TApplication::Close() is where most of MacApp’s cleanup takes place.
A TRICK for the especially lazy
Normally when you add TTidyApplication to your project you’ll have to modify your Make file. If you don’t want to do that you can include UTidyApplication.cp instead of UTidyApplication.h. It’s not pretty, and it will likely cause “duplicate symbol” link warnings, but in MPW you can get away with it.
MacApp 3.1 Considerations
TTidyApplication is an abstract class, so I chose not to use the DeclareClassDesc, DeclareClass, and DefineClass macros. Feel free to add them if you wish! The only deleterious consequence I noticed was that TTidyApplication does not appear in The Debugger’s class tree.
Pointers vs. Handles
Even when gPrintHandler and gNullPrintHandler pointed to the same object this code had no trouble in MacApp 3.0.
gPrintHandler = (TPrintHandler *)FreeIfObject(gPrintHandler);
gNullPrintHandler = (TPrintHandler *)FreeIfObject(gNullPrintHandler);
The first line would set both gPrintHandler and gNullPrintHandler to NULL, so that the call to FreeIfObject in the second line would do nothing and the code would work. It’s easy to forget, but Apple’s CFront silently dereferences all PascalObject descendants.
This code broke in MacApp 3.1. Now TObject no longer descends from PascalObject so gPrintHandler and gNullPrintHandler are pointers, not handles. Setting one to NULL has no effect on the other. To work around this I replaced the code with this:
if (gPrintHandler == gNullPrintHandler)
gPrintHandler = (TPrintHandler *) FreeIfObject(gPrintHandler);
gNullPrintHandler = NULL;
gPrintHandler = (TPrintHandler *) FreeIfObject(gPrintHandler);
gNullPrintHandler = (TPrintHandler *) FreeIfObject(gNullPrintHandler);
I have made no attempt to dispose of Pointers and Handles allocated by MacApp. If anyone implements a class that does this I’d appreciate a copy!
Applying TTidyApplication to one of the MacApp samples takes a few minutes. I’ve tried it with Skeleton and Calc; checking with a breakpoint at LastChance() reveals that in fact that no objects remain.
Well, hardly any objects remain. If you use MacApp 3.1 with VoodooMonkey you’ll see two objects, both TDynamicArrays, although The Debugger does not display them. The two, ClassDesc::fgClassDescListByName and ClassDesc::fgClassDescListByID,
are private class static variables, and I couldn’t figure out a way to reference them without modifying MacApp. I decided I could live with it. The best way to fix this would be for the MacApp team to add a ClassDesc::DeInitUClassDesc method. When I say “fix”, though, keep in mind that it’s not really a bug.
What about Native C++ Classes?
There’s no reason that this technique can’t be applied to any kind of objects, or indeed any kind of memory allocations. It does require a debugging tool that can display allocated objects. For MacApp 3.0.1 users Jasik’s debugger fits the bill nicely for TObject descendants, and well enough for memory allocated on the heap.
If your favorite debugger won’t display the kinds of memory you’d like to see you may be able to modify your memory allocator to provide a list of allocated items. There could be an article in it!
Why not just use the “Leaks” DCMD?
Leaks requires a “steady state” for its setup. Because of its nature it cannot detect leaks during application startup. When I tried adding TTidyApplication to one large application I discovered a memory leak during the allocation of a database server object. I can’t think how else the leak would have been discovered, though I’d love to hear about alternative techniques!
TTidyApplication is useful when polishing an application. One can execute a testing suite and then find out when the application terminates whether or not anything was forgotten.
What about SourceBug?
SourceBug shows all classes which have ever been allocated in its “Class” pane. VoodooMonkey is better for the present purpose. It only shows a class in its Class pane if at least one object of that class remains allocated. VoodooMonkey’s inspector works only with MacApp 3.1, and you must build with the “-Inspector” flag.
TTidyApplication provides a quick and easy way to check for objects which an application forgets to free. The code is available on this issue’s FrameWorks disk. I’ve also posted it to MacApp3Tech$ and placed a copy on AppleLink. Anyone who would like to place it on an ftp site or elsewhere is welcome to do so.
TTidyApplication would have been difficult or impossible to write had Apple not shipped source code with MacApp. I’d like to thank Apple for their wise decision, and take the opportunity to remind everyone how much more useful a framework can be when its source code is available!
Even if one doesn’t use TTidyApplication I’d like to suggest the use of the qCleanUpAtQuit variable. It serves as a kind of “executable comment”; with luck it will not only help an application quit a little faster, it will also make the code a little easier to understand.