Dynamic memory allocation¶
Dynamic data structures are such structures, for which memory
is allocated (and deallocated) dynamically,
based on the commands written by the programmer.
In C++, these commands are new
and delete
, and they
will be introduced more precisely below.
These commands are executed at run time during program execution, and thus,
also memory is allocated (and deallocated) at run time.
Dynamic data structures usually consist of
several elements of the same type.
Such an element can be defined as a C++ struct
with several fields.
The elements are linked together such that each element has a link field
(pointer) to another element(s) of the same structure.
Examples of dynamic data structures are linked lists, stacks, queues, trees, networks etc. These structures are typically defined as classes, and they can be operated by the member functions specific for the structure in question. As with any class, the programmer is responsible for implementing the member functions.
From these structures, we will introduce a linked list more precisely later on this round.
Commands for memory allocation and deallocation¶
Until now, we have only used automatic variables, which means that allocating (reserving) memory for them and afterwards deallocating (releasing) it was automated:
- At variable definition, the compiler has taken care of finding the necessary amount of memory somewhere and allocated it.
- When a variable reached the end of its lifetime, [1] the compiler, again, has deallocated the memory that was no longer needed.
However, there are many situations when a programmer has to be able to control the lifetimes of variables (starting from the allocation of memory for a variable and ending with the deallocation of the reserved memory) in order to create dynamic data structures. When the control over the lifetime of a variable is completely the programmer’s responsibility, the variable is called a dynamic variable. The mechanisms and tools used for controlling dynamic variables are called dynamic memory management.
C++ has two basic commands you can use to allocate
and deallocate dynamic memory: new
and delete
.
If there is not enough free memory space when the operation new
is
executed, an exception occurs and the program execution terminates.
However, handling that exception is, even on a conceptual level,
so challenging that, on this course, we will
pretend that we never run out of memory.
The programmer can allocate a new dynamic variable with the command new
:
- The lifetime of a variable begins at the moment when
new
succeeds to allocate memory for the variable. - A dynamic variable does not have a name, but the operation
new
returns a pointer, the value of which reveals where the new dynamic variable is located within the main memory. - To make the dynamic variable useful, its address (the return value
of
new
) needs to be stored in a pointer variable. - If the variable is a class-type one,
new
will take care of calling the constructor.
When you no longer need the dynamic variable, and you want to
get rid of it, you deallocate the memory with the command delete
:
- The variable reaches the end of its lifetime, and the memory that was allocated to it is deallocated for other uses.
- If a class-type variable has a destructor defined,
delete
will call the destructor.
Because the compiler does not automate anything when handling a dynamic
variable, it is important for you to remember that each variable allocated
with new
must also be deallocated with delete
when
you no longer need it.
If you forget to do this or you mess things up and end up with a situation
where [2] not all the memory allocated with new
can be deallocated,
what you then have is called a memory leak:
The program continues to keep memory locations allocated even though it
no longer needs them.
A memory leak is a bad thing, especially in programs with long execution times. If you constantly allocate memory but never deallocate some of it, the amount of main memory used by the program increases, and the whole system is burdened.
The first example about dynamic memory management:
int main() {
int* dyn_variable_address = nullptr;
dyn_variable_address = new int(7);
cout << "Address: " << dyn_variable_address << endl;
cout << "Start: " << *dyn_variable_address << endl;
*dyn_variable_address = *dyn_variable_address * 4;
cout << "End: " << *dyn_variable_address << endl;
delete dyn_variable_address;
}
*
operator.delete
operator.When you compile and execute the code, you receive the following print:
Address: 0x1476010
Start: 7
End: 28
Here and in the other execution examples that print pointer values to the screen, please remember that the value of the memory address can be different at different executions.
[1] | The local variables reach the end of their lifetime and deallocate the memory when the execution of the program leaves the block where the variable was defined. When the lifetime of an object ends, also the lifetimes of its member variables end, and memory is deallocated. |
[2] | You can, for example, accidentally lose the value of the pointer variable that stores the memory address of a dynamically allocated variable. |
dyn_variable_address
, so that we have a storage place for the address of the variable we are going to create on the next line.