There are three types of memory allocation types in C++:
Static memory allocation: Happens for static and global variables that we create.
Automatic memory allocation: Happens whenever there is an initialization of variables inside blocks and functions.
Dynamic memory allocation: Programmers do it manually to dynamically create objects.
Issues with static and automatic allocations
They need to know the variable size at the compile time which restricts us writing program where we need dynamic creation of objects. We kind of solve it by guessing the size that may require and compile the program, but it has several issues such as:
Memory wastage
For example, if we guess the user will input at max 100 elements and create an array of 100 elements, we are basically occupying space for 100 ints. If user provides only 10 elements, then 90 spaces are wasted.
int array[100];
Stack Overflow
Variables (including fixed size array) created in the function are stored in program memory called stack which has very limited size (probably 1MB) on most systems. If we create an array of 1000000 integers which is of size 4MB, inside the function and run the program, the program would fail.
int main(int argc, char const *argv[]){ int arr[10000000]; return 0;}
This would overflow the stack and the program would fail.
Tip
Creating the array in global namespace would be get space allocated in data region of the program which has more space than stack. This program would not crash:
int arr[10000000];int main(int argc, char const *argv[]){ return 0;}
Array overflows
If we create an array of 100 elements and try to store 101 elements in runtime. This usually happens when we read input from file and buffer can’t store more data than its fixed size.
Dynamic allocation
To solve all those issues, C++ provides dynamic memory allocation. Dynamic memory allocation happens at runtime. For example, following code snippet shows the dynamic allocation of an int variable using new operator:
int *ptr{new int};
new operator creates memory for object of type int and return address of the memory which we store in pointer ptr.
We can then use this pointer to store a value at the location:
*ptr = 2;
We could initialize the value while dynamically allocating memory to the variable as shown below:
int *ptr{new int{6}};
Tip
If there is not enough memory to allocate, new throws an exception. We can avoid that using std::nothrow constant. With this, new returns a nullptr if space is not allocated. For example:
int *ptr{new (std::nothrow) int{1}};
How dynamic allocation works?
When a program runs, its gets memory from the operating system for it to work. The assigned memory is divided into parts:
Memory for text (code of the program).
Data, where global and static variables are stored.
Stack, where function local variables are stored.
Heap, where dynamically allocated variables are stored.
When we use new operator to allocate memory for a variable, operating system allocates the space in the heap. We can use that space store the value. When the application is completed, the space is returned back to the operating system.
Returning back the allocated space
Once we are done with the application of the variable, we can return back the space allocated for it. We can use delete operator and provide it the pointer which contains the address of the allocated space.
delete ptr;
It is to be noted that if you provide a pointer which does not contain the address of the allocated space and contains address of a local variable, it would result in undefined behavior. It can crash the program.
For example:
int value{1};int *ptr{&value};std::cout << *ptr << '\n';delete ptr; // program would crash
The same would happen if we delete a dangling pointer (pointer to deallocated space using delete). For example:
delete ptr; // fine for the first time// ptr is a dangling pointerdelete ptr; // doing this again would do bad things to the program.
So, we should assign a nullptr to the pointer after deallocating the space.
delete ptr;ptr = nullptr;
The advantage of this is that we can change for null pointer before dereferencing it and providing a null pointer to delete does nothing.
delete ptr;ptr = nullptr;delete ptr; // does nothing.