DelphiBasics
  Home  |  Avoiding memory leaks
 Documents
 Tutorials

 Writing your first program
 Writing your second program
 Amending this program

 Delphi data types
   Numbers
   Text (strings and chars)
   Sets and enumerations
   Arrays
   Records

 Programming logic
   Looping
   SubRoutines
   Exception handling

 Dates and times

 Files

 Pointers

 Printing text and graphics

 Object Orientation basics
   Memory leaks!
   Inheritance
   Abstraction
   Interfaces
   An example class

 References

 Standard components

 Articles

 A brief history of Delphi

 Usability : file handling

 Usability : reference books

 Author links

  Avoiding memory leaks
Memory and Object Orientation
Object Orientation has transformed the process of application development. It has allowed complex code to be written in nicely encapsulated modules (objects). When you create an object, Delphi handles the memory allocation for the object as you call the Create method of the object class.
 
But there is a down side to this automation that is often overlooked, especially by newcomers, giving rise to memory leaks.
 
What are memory leaks?
Put simply, every time you no longer use an object in your code, you should delete it, thereby freeing the memory it was allocated. If you don't do this, your program can allocate more and more memory as it runs. This failure to discard unwanted blocks of memory is called a memory leak. Run the program long enough and you will use up the memory resources of your PC and the PC will slow down and eventually hang.
 
This is a very real problem in many programs, including commercial applications. If you are serious about your code, you should follow the principles in this tutorial.
 
Why Delphi cannot free your memory for you
Delphi implements objects by reference. When you declare an object :
 
 var
   rover : TCar;

you are simply declaring a reference to an object. When you create an object for this reference :
 
 begin
   rover := TCar.Create;

Delphi allocates memory for the object, and executes the Create constructor method of the class to do any object initialisation required. The rover variable now points to this new object. There is no magic link from the object to the variable - simply a reference.
 
You can make a second reference to the object :
 
 var
   rover : Tcar;
   myCar : TCar;
 
 begin
   rover := TCar.Create;
   myCar := rover;

The myCar assignment simply makes the myCar variable point to the object that rover points to. No copying of the object is done. Only one object exists at this time. And it is not magically linked to either variable.
 
Delphi does not keep track of who has referred to the object because it cannot easily keep track of all possible variables that might refer to the object. So, for example, you could set both of these variables to nil and the object will still persist.
 
How the memory leaks normally occur
In many parts of many programs, you will have a function or procedure that creates one or more objects in order to carry out it's operation. Here is an example :
 
 procedure PrintFile(const fileName : string);
 var
   myFile   : TextFile;
   fileData : TStringList;
   i        : Integer;
 
 begin
  // Create the TSTringList object
   fileData := TStringList.Create;     // This allocates object memory
 
  // Load the file contents into the string list
   fileData.LoadFromFile(fileName);    // Expands the object memory size
 
  // Open a printer file
   AssignPrn(myFile);
 
  // Now prepare to write to the printer
   ReWrite(myFile);
 
  // Write the lines of the file to the printer
   for i := 0 to fileData.Count-1 do
     WriteLn(myFile, fileData[i]);
 
  // Close the print file
   CloseFile(myFile);
 end;

Each time this procedure is called, a new TStringList object is created and filled with the contents of a file. You might think that Delphi would discard the old object when you are assigning the new object to fileData. But this does not happen because Delphi does not know if this is the only variable referring to the object.
 
So you must add the following code to the end of the procedure to free up the memory:
 
  // Free up the string list memory
   FreeAndNil(fileData);

This allows the procedure to be called as many times as you want without more memory being allocated for the string list object each time. We have avoided a memory leak.
 


 

Memory used within an object
Classes frequently allocate objects in the constructor and in methods. The allocations in the methods should be freed in line, as described above.
 
But the objects allocated in the constructor should be freed in a Destructor method. It is like a converse process. But do not code these Free (or FreeAndNil) statements glibly. Take care to see how the class carries out allocations. It may, for example, create objects for a caller for these to persist after the current object is destroyed.
 
Here is an example of a class with appropriate memory handling :
 
 unit MyClass;
 
 interface
 
 uses
   Classes;
 
 type
   TMyClass = class
   private
     fileData : TStringList;
   published
     Constructor Create(const fileName : string);
     Destructor  Destroy; override;
     procedure   PrintFile;
   end;
 
 implementation
 
 uses
   Printers, Dialogs, SysUtils;
 
 // Constructor - builds the class object
 constructor TMyClass.Create(const fileName: string);
 begin
  // Create the string list object used to hold the file contents
   fileData := TStringList.Create;
 
  // And load the file data into it
   try
     fileData.LoadFromFile(fileName);
   except
     ShowMessage('Error : '+fileName+' file not found.');
   end;
 end;
 
 // Destructor - frees up memory used by the class object
 destructor TMyClass.Destroy;
 begin
  // Free up the memory used by the string list object
   FreeAndNil(fileData);
 
  // Call the parent class destructor
   inherited;
 end;
 
 // Print the file passed to the constructor
 procedure TMyClass.PrintFile;
 var
   myFile   : TextFile;
   i        : Integer;
 
 begin
  // Open a printer file
   AssignPrn(myFile);
 
  // Now prepare to write to the printer
   ReWrite(myFile);
 
  // Write the lines of the file to the printer
   for i := 0 to fileData.Count-1 do
     WriteLn(myFile, fileData[i]);
 
  // Close the print file
   CloseFile(myFile);
 end;
 
 end.

Note that in this example, the object holds the file, and the caller calls the PrintFile method without respecifying the file name. When an object of this class is destroyed, the string list containing the file contents is also destroyed.
 
On Windows XP, the Task Manager shows the memory allocated to running processes. It is useful to observe the allocation amount for your application as you use it. There is likely to be some upping of memory useage as you use different functions, but repeated calls to the same function should normally yield a stable memory allocation.
 
Some more subtle object allocations
There are times when it is not obvious when memory is allocated, or when memory should be discarded. Just as Delphi cannot keep track of object references, you may not easily follow references in your code. This is particularly true when you call routines that create objects for you.
 
Delphi itself has Run Time Library functions that allocate memory. In particular, the FindFirst, FindNext and FindClose set of functions is worthy of a mention.
 
When FindFirst runs, it attempts to find the first file in a folder that satisfies your search criteria. If it finds a file, then it creates an object that keeps track of where it found this file. This is needed for subsequent calls to FindNext.
 
However, it means that you are obliged to call FindClose in order to discard the memory used by this object. So here is an example of object allocation without an explicit call to a Create constructor.
 
You have been warned!
 
 
 

Delphi Basics © Neil Moffatt All rights reserved.  |  Home Page