close

Memory leaks and Memory corruptions

The two worst bugs when dealing with manual memory management

Both memory leaks and memory corruption are very dangerous bugs that programs that perform manual memory management are subject to.

Those, when happen, are very difficulty to locate the source and cause the program to waste resources and behave in unpredictable ways.

Memory Leaks

The most famous and least dangerous of the two, those happen when you allocate memory, but don't free it after you're done using it.

  procedure example;
  var
    object: TMyObject;
  begin
    object := TMyObject.Create();
    //Code using object...
  end;

In the procedure above we allocate memory for an object, but doesn't free the memory allocation after using it. This is a memory leak: Unused memory that is not released.

Every time the procedure is called, we will be allocating memory to the heap and leaving it there forever... Well, at least until the program crashes due to not having any memory left.

You should free every object after you're done using them. The best practice is to include it in a try-finally exception handler like in the code below:

  procedure example;
  var
    object: TMyObject;
  begin
    object := TMyObject.Create();
    try
      //Code using object...
    finally
      object.Free();
    end;
  end;

This will ensure the object is freed no matter what happens in the code.

Also, a good option to help with detecting memory leaks is to use the -gh flag with the fpc compiler: fpc -gh myfile.lpr

Memory Corrpution

The worst thing you can have in your code is Memory Corruption.

  procedure example;
  var
    arr: TMyDynamicArray;
    i: Integer;
  begin
    SetLength(arr, 10);
    for i := 0 to 10 do
      //Code writing to the array.
  end;

What happens in the code above? The code writes data to the indexes 0 to 10 of the array. But the Length of the array is 10, meaning its indexes range from 0..9, this means the memory after index 9 are unallocated to the array and thus I'm writing to unallocated memory.

This is memory corruption: Writing data to unallocated memory.

This is the worst bug possible because it can cause unpredictable behavior.

If the memory was unallocated in the program as whole, the example code will crash on the spot, this is the best case scenario, as it allows you to fix it right away.

But if the memory is already allocated in another part of the program, then one of the following will happen:

  • The program will run just fine.
  • The program will behave in an unexpected way in another part of code.
  • The program will just randomly crash in another part of code.

The two last things will cause you to think the part in which the program misbehaved/crashed is bugged somehow if you're not aware of memory corruption, meaning you will waste time debugging the wrong thing.

Preventing Memory Corruption

Use range checking in the development phase of your programs: {$R+}.

  program example;
  {$R+}
  begin
  end.

Placing this, will tell the compiler to generate range checking code to prevent your code from writing outside of the correct memory range, you can read more about that here.

It's a good idea to remove range checking for release builds, as it impacts performance.

You should also use Low() and High() functions when iterating over arrays.

  procedure example;
  var
    arr: TMyDynamicArray;
    i: Integer;
  begin
    SetLength(arr, 10);
    for i := Low(arr) to High(arr) do
      //Code writing to the array.
  end;

The code above will correctly iterate in the bounds of the array. Low() returns the lowest index of the array, High() returns the highest.

24/01/2020