Dynamic memory management with FreeBASIC

Managing Dynamic Memory (Allocation / Deallocation) with FreeBASIC

Preamble:

   FreeBASIC supports three basic types of memory allocation:
      - Static allocation occurs for static and global variables: Memory is 
      allocated once when the program runs and persists throughout the life 
      of the program.
      - Stack allocation occurs for procedure parameters and local 
      variables: Memory is allocated when the corresponding block is 
      entered, and released when the block is left, as many times as 
      necessary.
      - Dynamic allocation is the subject of this page.

   Static allocation and stack allocation have two things in common:
      - The size of the variable must be known at compile time.
      - Memory allocation and deallocation occur automatically (when the 
      variable is instantiated then destroyed). The user can not anticipate 
      the destruction of such a variable.

   Most of the time, that is fine. However, there are situations where one 
   or other of these constraints cause problems (when the memory needed 
   depends on user input, the sizing can only be determined during 
   run-time).

   If the size of everything must be declared at compile time, the best is 
   try to estimate the maximum size of the variables needed and hope this 
   will be enough.
   This is a bad solution for at least third reasons:
      - First, it leads to wasting memory if it is not fully used.
      - Second, most normal variables are allocated in a part of the memory 
      called the stack. The amount of stack memory for a program is usually 
      quite low (1 MB or 2 MB by default). If exceeding this number, the 
      stack overflow will occur, and the program will abort.
      - Third, and most importantly, this can lead to artificial 
      limitations and / or overflows. What happens if the required memory 
      becomes greater than the reserved memory (stopping the program, 
      emitting message to user, ...).

   Fortunately, these problems are easily solved through the dynamic 
   allocation of memory. Dynamic memory allocation is a way of running 
   programs to request memory from the operating system when needed. This 
   memory does not come from the program's limited stack memory, but it is 
   rather allocated from a much larger memory pool managed by the operating 
   system called heap. On modern machines, the heap can have a size of 
   several gigabytes.

Keywords for dynamic memory allocation
   There are two sets of keyword for dynamic allocation / deallocation:
      - Allocate / CAllocate / Reallocate / Deallocate: for raw memory 
      allocation then deallocation, for simple pre-defined types or buffers 
      (as numeric pre-defined types, user buffer, ...).
      - New / Delete: for memory allocation + construction then destruction 
      + deallocation, for object type (as var-len strings, UDTs, ...).

   Mixing keywords between these two sets is very strongly discouraged when 
   managing a same memory block.

Dynamic memory allocation management by using Allocate / Callocate / 
Reallocate / Deallocate
   For each keyword, see the detailed syntax, precise description and 
   examples in the individual documentation pages.

   Additional functionalities and tips for use:
      - Allocate / CAllocate / Reallocate allows to know if memory 
      allocation is successful (otherwise a null pointer is returned).
      - Even if the allocated memory size is requested for 0 Byte, a non 
      null pointer is returned and its value should be used to then release 
      the allocation (except for Reallocate(pointer, 0) which behaves 
      similar to Deallocate).
      - For memory deallocation, Deallocate can be called on any type of 
      pointer (with the right value anyway).
      - If the user absolutely wants to use this type of allocation for an 
      object (for example to be able to do reallocation), it's up to him to 
      manually call if necessary the constructor and the destructor (by 
      using member access operator) at the right way.
      - After deallocation, the pointer value becomes invalid (pointing to 
      an invalid memory address), and its reuse (for dereferencing or 
      calling Deallocate again) will result in undefined behavior.

Dynamic memory allocation management by using New / Delete
   For each keyword, see the detailed syntax, precise description and 
   examples in the individual documentation pages.

   Additional functionalities and tips for use:
      - Before, New did not signal if memory allocation was successful 
      (program hangs). Problem solved from fbc rev 1.06, by returning a 
      null pointer if New fails.
      - Even if the allocated memory size is requested for 0 Byte (New 
      predefined_datatype[0]), a non null pointer is returned and its value 
      should be used to then release the allocation.
      - For object destruction and memory deallocation, Delete must be 
      called on a proper typed pointer (otherwise the object destructor 
      will not be called or miscalled, and that may result in a memory leak 
      or even a crash with Delete[]).
      - The user can also use this type of allocation for a simple 
      pre-defined types (except for the fix-len strings), but this does not 
      functionally add anything, except a simpler usage syntax for 
      allocation.
      - After destruction + deallocation, the pointer value becomes invalid 
      (pointing to an invalid memory address), and its reuse (for 
      dereferencing or calling Delete again) will result in undefined 
      behavior.
      - If used, the special Placement New (using memory already allocated) 
      induces only object construction, so use Delete is forbidden (to 
      avoid double request of deallocation). If necessary, the only 
      destruction of the object must be manually do by user (calling the 
      destructor by using member access operator).

Variant by using Redim / Erase
   FreeBASIC also supports dynamic arrays (variable-length arrays).
   The memory used by a dynamic array to store its elements is allocated at 
   run-time in the heap. Dynamic arrays can contain simple types as well as 
   complex objects.
   By using ReDim, the user does not need to call the constructor / 
   destructor because ReDim does this automatically when adding / removing 
   element. Erase then destroys all the remaining elements to completely 
   free the memory allocated to them.

Use cases by comparing 4 methods
   Usage example on a set of objects, by comparing 4 methods:
      - 3 then 4 objects: CAllocate, Reallocate, Deallocate, (+ 
      .constructor + .destructor).
      - 3 objects: New, Delete.
      - 3 objects: Placement New, (+ .destructor).
      - 3 then 4 objects: ReDim, Erase.
   Type UDT
      Dim As String S = "FreeBASIC"              '' induce an implicit constructor and destructor
   End Type

   ' 3 then 4 objects: Callocate, Reallocate, Deallocate, (+ .constructor + .destructor)
   Dim As UDT Ptr p1 = CAllocate(3, SizeOf(UDT))  '' allocate cleared memory for 3 elements (string descriptors cleared,
                                       ''     but maybe useless because of the constructor's call right behind)
   For I As Integer = 0 To 2
      p1[I].Constructor()                        '' call the constructor on each element
   Next I
   For I As Integer = 0 To 2
      p1[I].S &= Str(I)                          '' add the element number to the string of each element
   Next I
   For I As Integer = 0 To 2
      Print "'" & p1[I].S & "'",                 '' print each element string
   Next I
   Print
   p1 = Reallocate(p1, 4 * SizeOf(UDT))           '' reallocate memory for one additional element
   Clear p1[3], 0, 3 * SizeOf(Integer)            '' clear the descriptor of the additional element,
                                       ''     but maybe useless because of the constructor's call right behind
   p1[3].Constructor()                            '' call the constructor on the additional element
   p1[3].S &= Str(3)                              '' add the element number to the string of the additional element
   For I As Integer = 0 To 3
      Print "'" & p1[I].S & "'",                 '' print each element string
   Next I
   Print
   For I As Integer = 0 To 3
      p1[I].Destructor()                         '' call the destructor on each element
   Next I
   Deallocate(p1)                                 '' deallocate the memory
   Print

   ' 3 objects: New, Delete
   Dim As UDT Ptr p2 = New UDT[3]                 '' allocate memory and construct 3 elements
   For I As Integer = 0 To 2
      p2[I].S &= Str(I)                          '' add the element number to the string of each element
   Next I
   For I As Integer = 0 To 2
      Print "'" & p2[I].S & "'",                 '' print each element string
   Next I
   Print
   Delete [] p2                                   '' destroy the 3 element and deallocate the memory
   Print

   ' 3 objects: Placement New, (+ .destructor)
   ReDim As Byte array(0 To 3 * SizeOf(UDT) - 1)  '' allocate buffer for 3 elements
   Dim As Any Ptr p = @array(0)
   Dim As UDT Ptr p3 = New(p) UDT[3]              '' only construct the 3 elements in the buffer (placement New)
   For I As Integer = 0 To 2
      p3[I].S &= Str(I)                          '' add the element number to the string of each element
   Next I
   For I As Integer = 0 To 2
      Print "'" & p3[I].S & "'",                 '' print each element string
   Next I
   Print
   For I As Integer = 0 To 2
      p3[I].Destructor()                         '' call the destructor on each element
   Next I
   Erase array                                    '' deallocate the buffer
   Print

   ' 3 then 4 objects: Redim, Erase
   ReDim As UDT p4(0 To 2)                        '' define a dynamic array of 3 elements
   For I As Integer = 0 To 2
      p4(I).S &= Str(I)                          '' add the element number to the string of each element
   Next I
   For I As Integer = 0 To 2
      Print "'" & p4(I).S & "'",                 '' print each element string
   Next I
   Print
   ReDim Preserve p4(0 To 3)                      '' resize the dynamic array for one additional element
   p4(3).S &= Str(3)                              '' add the element number to the string of the additional element
   For I As Integer = 0 To 3
      Print "'" & p4(I).S & "'",                 '' print each element string
   Next I
   Print
   Erase p4                                       '' erase the dynamic array
   Print

   Sleep
         
 Output:

   'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'
   'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'  'FreeBASIC3'

   'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'

   'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'

   'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'
   'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'  'FreeBASIC3'
   			

Specific dynamic image buffer allocation by using Imagecreate / 
Imagedestroy
   ImageCreate allocates and initializes storage for an image.
   The resulting image buffer can be used in drawing procedures while in 
   any screen mode, and across mode changes,  as long as the color depth of 
   the image buffer matches that of the graphics screen.
   ImageDestroy destroys and deallocates storage for an image.

See also
   * New and Delete
   * Variable-length Arrays
   * Dynamic Object and Data Lifetime

