|
| 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 : | |
|
|
| 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! | |
| | | | |