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;
}
Creating the pointer 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.
Allocating a new dynamic variable, and initializing its value to 7 and store its address for later use.
Using the dynamic variable with its memory address and the * operator.
When you no longer need the dynamic variable, you must deallocate the memory reserved for it with the 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.