I'll end this chapter devoted to the structure of Delphi applications with a section devoted to memory management. This topic is very complex, and probably worth an entire chapter of its own; here I can only scratch it and provide a few indications for further experiments. For more detailed memory analysis you can refer to the many Delphi add-on tools addressing memory verification and control, including MemCheck, MemProof, MemorySleuth, Code Watch, and AQTime.
Delphi has a memory manager, accessible using the GetMemoryManager and SetMemoryManager functions of the System unit. These functions allow you to retrieve the current memory manager record or modify it with your custom memory manager. A memory manager record is a set of three functions used to allocate, deallocate, and reallocate memory:
type TMemoryManager = record GetMem: function(Size: Integer): Pointer; FreeMem: function(P: Pointer): Integer; ReallocMem: function(P: Pointer; Size: Integer): Pointer; end;
It's important to know how these functions are called when you create an object, because you can hook in two different steps. As you call a constructor, Delphi invokes the NewInstance virtual class function, defined in TObject. Because this is a virtual function, you can modify the memory manager for a specific class by overriding it. To perform the memory allocation, however, NewInstance typically ends up calling the GetMem function of the active memory manager, which provides you with your second chance to customize the standard behavior.
Unless you have very special needs, you won't generally need to hook into the memory manager to modify how memory allocation works. However, I find it quite useful to hook into a memory manager to determine whether memory allocation is working properly— that is, to be sure the program has no memory leaks. For example, you can override a class's NewInstance and FreeInstance methods to keep a count of the number of objects of the class being created and destroyed and check if the total is zero.
An even simpler technique is to perform the same test over the number of objects allocated by the entire memory manager. In the early versions of Delphi doing so required extra code, but the memory manager exposes two global variables (AllocMemCount and AllocMemSize) that can help you determine what is going on in the system.
For more detailed information about how the memory manager is working internally, you can use the GetHeapStatus function. It is available only on Windows, because it provides information about the status of the memory allocator. On Linux, the RTL uses the system allocator, not a custom memory allocator.
The simplest way to determine whether your program is handling memory properly is to test whether AllocMemCount goes back to zero. The problem is deciding when to perform such a test. A program begins by executing the initialization section of its units, which usually allocate memory freed by the respective finalization sections. To guarantee that your code is executed at the very end, you must write it in the finalization section of a unit and place it at the very beginning of the units list in the project source code file. You can see such a unit in Listing 8.1. This is the SimpleMemTest unit of the ObjsLeft example, which has a sample form with a button to show the current allocations count and a button to create a memory leak (which is then caught when the program terminates).
unit SimpleMemTest; interface implementation uses Windows; var msg: string; initialization finalization if AllocMemCount > 0 then begin Str (AllocMemCount, msg); msg := msg + ' heap blocks left'; MessageBox (0, PChar(msg), 'Memory Leak', MB_OK); end; end.
When writing code that involves checking, implementing, or extending memory manager code, you have to avoid using any high-level functions, as they might affect the memory manager. For example, in the SimpleMemTest unit, I couldn't include the SysUtils unit because it allocates memory. I had to resort to the traditional Turbo Pascal Str function instead of Delphi's standard IntToStr conversion.
This program is handy, but doesn't really help you understand what went wrong. For this purpose, powerful third-party tools are available (some of which have free trial versions), or you can refer to my custom memory manager, which tracks memory allocations (described in Appendix A of this book).