How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Forum for discussion about the documentation project.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

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 article.

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 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.

1) 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.
2) 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 nul 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.
3) 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).
4) 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.
5) 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.

    Code: Select all

    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
    

    Code: Select all

    'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'
    'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'  'FreeBASIC3'
    
    'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'
    
    'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'
    
    'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'
    'FreeBASIC0'  'FreeBASIC1'  'FreeBASIC2'  'FreeBASIC3'
Last edited by fxm on Dec 24, 2018 7:24, edited 7 times in total.
jj2007
Posts: 2326
Joined: Oct 23, 2016 15:28
Location: Roma, Italia
Contact:

Re: How to Manage Dynamic Memory in FB

Post by jj2007 »

fxm wrote:Static and automatic 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).
Can you deallocate a static/global variable? If it's in the data section, you can't. Maybe the difference between static and global variables should be explained.

Otherwise, it's very well written, compliments.
marcov
Posts: 3462
Joined: Jun 16, 2005 9:45
Location: Netherlands
Contact:

Re: How to Manage Dynamic Memory in FB

Post by marcov »

I mainly have a reservations with the names of the three types. I'd use stack based and heap based for automatic and dynamic because:

- There is a limited possibility for dynamic allocation on the stack (alloca in C)
- I would call automatic allocation stack allocation and reserve automatic allocation for forms of heap allocation that auto free (like ref counting or GC)
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory in FB

Post by fxm »

I tried to improve the text a little without going into details off topic versus dynamic memory allocation.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

Added the paragraph: '4) Variant by using Redim / Erase'.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

Added the paragraph: '5) Use cases by comparing 4 methods'.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

fxm wrote:.....
Additional functionalities and tips for use:
  • New does not signal if memory allocation is successful (program hangs).
    (this exists in C++ by using the 'new (nothrow)' syntax, for FB: see the feature request #302)
    (problem solved from fbc rev 1.06, by returning a null pointer if New fails)
.....
I think the fix to the "#699 Bug on the "New[]" operator when attempting to allocate a negative number of elements" bug report must also close the "#302 Operator New returns null pointer if memory for object(s) cannot be allocated" feature request.


[edit]
Thanks for closing.
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by Tourist Trap »

Hi all,

hope everyone is fine. I've a question exactly related with the topic, that however I can't find answered in the excellent presentation. For instance, we have this:

Code: Select all

var w = (new UDT)->u
What happens in this very useful usage of NEW? We can call no DELETE on anything then I wonder if the temporary variable will be garbaged or what should we do?

Thanks.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

'New UDT' does not create a temporary UDT instance, but a dynamic UDT instance that can only be destructed and deallocated by 'Delete @instance'.
Otherwise, this "lost" instance will remain allocated and constructed until the end of the program.

To declare a temporary instance, you can use a syntax such as:
Var w = (@type<UDT>(...))->u
or
Var w = (@UDT(...))->u
Warning: such temporary instances are destructed (automatic destructor call) at the end of the line where there are declared, but the memory allocated for their field data is only freed when they go out of scope block.

Code: Select all

Type UDT
  Dim As String s
End Type

Dim As UDT Ptr pu

pu = New UDT
' is equivalent to:
'   pu = Allocate(Sizeof(UDT))
'   pu->Constructor()  '' if constructor exists

Delete pu
' is equivalent to:
'   pu->Destructor()  '' if destructor exists
'   Deallocate(pu)  
I do not know if in the documentation it is necessary to highlight that a temporary instance is constructed/destructed at the beginning/end of the line where it is declared, but that it uses some memory which has been allocated for all the scope block.
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by Tourist Trap »

fxm wrote:'New UDT' does not create a temporary UDT instance, but a dynamic UDT instance that can only be destructed and deallocated by 'Delete @instance'.
Otherwise, this "lost" instance will remain allocated and constructed until the end of the program.
This is a very important point.
The main problem here is that in vb.net, we use all the time the (new UDT) construct to define and use a variable meant to stay temporary (use it and forget I may say). For example, to pass a Point2d to a procedure, we can use foo(new Point2d). Of course vb.net has a garbage collector, and hide things related to pointers (nobody will think of New there as something related to pointers) so it's different.
However unless one started learning an idiom of the vb branch with freebasic, it will always be very attractive to use 'New' preceding a constructor call. And as you show very well, in FB this means an Allocate that will require explicitly a Deallocate.
This is this point that may really desserve some insistence. At least at the page where temporary udts are explained. (or as a compiler warning anticipating the confusion for vb.net users?)

That the destructor and the deallocate are not synchronised in the case where someone uses properly temporary udts, it can be interesting also (maybe at some page dealing with life-cycle of the udt variables). Because if not mentioned in the documentation, I don't see how one could guess about it.

In anyway thank you, the code example is very useful.
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

[url=https://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpNew]Operator New Expression[/url] documentation page wrote:.....
The New Expression operator dynamically allocates memory and constructs a specified data type.
.....
Objects created with New Expression operator must be freed with Delete Statement operator.
.....
[url=https://www.freebasic.net/wiki/wikka.php?wakka=KeyPgOpDelete]Operator Delete Statement[/url] documentation page wrote:.....
The Delete Statement operator is used to destroy and free the memory of an object created with New Expression operator.
.....
[url=https://www.freebasic.net/wiki/wikka.php?wakka=KeyPgTypeTemp]Temporary Types[/url] documentation page wrote:.....
A temporary object is destroyed at the end of execution of the statement (where it's defined), but its corresponding allocated memory is not released and remains available (unused) until going out the scope where statement is.
.....
I added this one last sentence on 2018-10-04.
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by Tourist Trap »

[/quote]
All is very good I think (I would only put the must in bold), until we confront the example I've given at first. In fact I simplified the affair so some details that could feed the reflection have been occulted.
I don't access a variable but a property of the UDT that I write (New UDT):

Code: Select all

var x = (New UDT)->Foo()
In general we could be right to reconstruct a UDT each time we use (New UDT) , but here in fact I needed just a dummy instance of the UDT to use its function Foo , which doesn't rely on the values of the UDTs fields.
I think then that I would not use at all (New UDT) if I could just have a kind of static version of the UDT for this purpose. Do you see an elegant workaround in this context? In other words I just need a pointer to the UDT that is always allocated at the same address for a usage as a static variable...

(edit) I found this that seems being working:

Code: Select all

var x =  (New(0) UDT)->Foo
Here Foo is a property that doesn't use any non-static stuff.
In my opinion here we don't need a delete operator because the placement is made at address 0. But I can be wrong.
(side note, when I'm with placement new, just a detail but when I see [ count ] in the doc, I think the squared brackets mean 'optional'. Maybe removing the spaces surrounding ' count ' would help slighly more because then the brackets couldn't signal an option. Or just telling people that those are true brackets. Or put double squared brackets which is compatible with the 1st syntax....)
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

The most used syntax is to call the member procedure on a null pointer:
var x = Cptr(UDT Ptr, 0)->Foo
(but yours is equivalent)

KeyPgOpPlacementNew → fxm [Added sentence for describing the Placement New[] operator syntax]
Tourist Trap
Posts: 2958
Joined: Jun 02, 2015 16:24

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by Tourist Trap »

fxm wrote:The most used syntax is to call the member procedure on a null pointer:
var x = Cptr(UDT Ptr, 0)->Foo
(but yours is equivalent)
Thanks.

In your last line, first post, you say that New is returning a null pointer if it fails. Not in the case of the Placement New?

Code: Select all

type UDT
   as integer ii
   declare property Foo() as integer
end type
property UDT.Foo() as integer
   return 9999
end property 

var x = New(-1) UDT    'should fail so x = 0
? x->Foo    'nothing returned
fxm
Moderator
Posts: 12107
Joined: Apr 22, 2009 12:46
Location: Paris suburbs, FRANCE

Re: How to Manage Dynamic Memory (Allocation / Deallocation) in FB

Post by fxm »

because:
Aborting due to runtime error 12 ("segmentation violation" signal) in ...\FBIde0.4.6r4_fbc1.06.0\FBIDETEMP.bas::()
  • (on 'var x = New(-1) UDT')
Post Reply