How to Allocate and Deallocate Memory in C++
Managing memory resources at runtime involves fundamental aspects of programming, such as allocating and deallocating memory in C++. Dynamic memory allocation enables you to allocate memory as your program is running instead of statically allocated memory, which is determined by the program's source code and allocated at compile time.
Memory deallocation and allocation are crucial operations in C++ for controlling dynamic memory used during program execution. This dynamic technique is crucial when working with data structures of different sizes, objects with various lifetimes, or when you need to manage memory resources for the best program performance effectively.
Techniques to Allocate and Deallocate Memory
C++ offers two main techniques to allocate and deallocate memory:
- New and deleted operators are used to perform these tasks. While delete is used for deallocation and, if necessary, activates object destructors, new is used for allocation and can initialize objects.
- C standard library Functions: C standard library Functions from the C Standard Library are also supported by C++. However, they are less frequently used because they need more type of safety and exhibit object-specific behaviour. Examples of these functions are malloc(), calloc(), and free(), which are used to allocate and deallocate memory.
The best method for managing memory in C++ is to use the new and delete operators because they are compatible with the language's object semantics.
Memory Allocation
Memory allocation is a fundamental programming method that enables you to request and reserve a portion of the computer's memory for your program's usage. Dynamic memory management depends on memory allocation, which in C++ occurs at runtime rather than compile time. Because it enables programs to adapt to changing data requirements, this dynamic allocation is required for tasks like dealing with arrays, linked lists, and dynamic data structures.
Methods for Allocating Memory
There are essentially two methods for allocating memory in C++:
- Static memory allocation:
At compile time, static memory allocation takes place.
- Before the program runs, memory is allotted for variables and data structures.
- The allocated memory has a fixed size and structure.
- Examples include predefined-size arrays, local variables, and global variables.
Syntax:
int staticArray[10];
2. Dynamic memory allocation:
- You can allocate memory as necessary due to dynamic memory allocation, which takes place during runtime.
- Dynamic memory allocation operators (new, new[]) or functions (new, malloc, calloc, etc.) are used to allocate memory.
- Depending on the program's needs, the allocated memory's size and structure may change.
- Data structures with changeable sizes can be managed well by dynamic allocation.
Dynamic Memory Allocation Proves Valuable in Various Scenarios
- When you must allocate memory for data structures with unknown sizes at compile time.
- When creating objects or arrays with variable lengths.
- When data needs to be shared among multiple functions or objects, it necessitates memory allocation.
- New operator:
In C++, the new operator is used for dynamic memory allocation, allowing you to request the system for memory at runtime. The lifetime operator is essential for managing memory and constructing objects with varied lifetimes.
Syntax:
T* pointerVariable = new T;
- T: The data type you wish to allocate memory for. It could be a user-defined class type or a built-in type (such as int or float).
- PointerVariable: A pointer that will be used to store the allocated object's memory address.
Program using new operator for memory allocation:
#include <iostream>
using namespace std;
int main() {
// Allocate memory for an integer using new
int* pointer = new int; // Dynamic memory allocation for an integer
// Check if memory allocation was successful
if (pointer == nullptr) {
cerr << "Allocating memory was unsuccessful" << endl;
return 1;
}
// Assign a value to the allocated memory
*pointer = 298;
// Display the value stored in a pointer
cout << "The value stored in pointer is: " << *pointer << endl;
// Deallocate the allocated memory using delete
delete pointer;
return 0;
}
Output:
To allocate memory for an array:
int* arrPtr = new int[15]; // Allocates memory for an integer array of size 15
Code:
#include <iostream>
using namespace std;
int main()
{
//pointer initialization to null
int* custom = nullptr;
// Request memory for the variable using the new operator
custom = new (nothrow) int;
if (!custom)
cout << "Memory allocation failed for 'custom'\n";
else
{
// Store value at allocated address
*custom = 123;
cout << "The value of 'custom' is: " << *custom << endl;
}
// Request memory for a single float variable using a new operator
float* request = new float(69.35);
cout << "The value of 'request' is: " << *request << endl;
// Request memory for an array of integers using a new operator
int num = 4;
int* sample = new (nothrow) int[num];
if (!sample)
cout << "Memory allocation failed for 'sample'\n";
else
{
for (int k = 0; k < num; k++)
sample[k] = k + 4;
cout << "Values stored in 'sample': ";
for (int j = 0; j < num; j++)
cout << sample[j] << " ";
}
// Free the allocated memory
delete custom;
delete request;
// Free the block of allocated memory
delete[] sample;
return 0;
}
Output:
Using a New Operator vs a Regular Array Declaration
Declaring a standard array and allocating a memory block using new are two different things. The main distinction is that normal arrays are released by the compiler (or, if local, the function, if the array is local when the function returns or completes). Dynamically allocated arrays, on the other hand, always stay in place until either the programmer deallocates them or the program ends.
What If There isn't Enough Memory Accessible When it's Needed?
In C++, the new operator signals a memory allocation failure by returning a NULL pointer or throwing a bad_alloc exception when the nothrow specifier is not used.
Syntax for nothrow:
int *pointer = new(nothrow) int;
if (!pointer)
{
cout << "The Memory allocation has been failed\n";
}
2. Functions of malloc and calloc in the C Standard Library:
C++ additionally includes the malloc and calloc memory allocation functions for C code compatibility.
Without using constructors, these functions allocate memory.
Using malloc to allocate memory:
int* numPtr = (int*)malloc(sizeof(int)); // Allocates memory for a single integer
Using calloc, one can allocate memory and initialize it to 0:
int* arrPtr = (int*)calloc(10, sizeof(int)); // Allocates memory for an integer array of size 10
- Malloc Function:
The malloc function (memory allocation) is called when memory needs to be allocated. It returns a reference to the first byte of the memory that was allocated.
Program:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *pointer;
// Allocate memory for an integer using malloc
pointer = (int *)malloc(sizeof(int));
// Check if memory allocation was successful
if (pointer == NULL) {
perror("Memory allocation failed"); // Print an error message if allocation fails
return 1;
}
// Assign a value to the allocated memory
*pointer = 439;
// Display the value stored in a pointer
printf("The value stored in pointer is: %d\n", *pointer);
// Deallocate the allocated memory using free
free(pointer);
return 0;
}
Output:
Explanation:
- The headers for input/output operations and memory allocation functions are <stdio.h> and <stdlib.h>.
- Malloc allocates memory for an integer, and the pointer to that memory is stored in the variable pointer.
- By comparing the Pointer to NULL, we determine whether the memory allocation was successful, and if it was, we print an error message.
- We set the memory referenced by the pointer to 439.
- We display to the console the value that has been stored in the pointer. We finally deallocate the allocated memory using the free function to stop memory leaks.
Note:
In C++, the free operator is not commonly used; instead, memory allocated with malloc is typically released using free to deallocate dynamically allocated memory.
- Calloc function:
When allocating memory for an array of elements, the calloc function (contiguous allocation) initializes all the memory's bytes to zero. Usage is equivalent to using malloc.
Program:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *pointer;
// Allocate memory for an array of integers (size 5) using calloc
pointer = (int *)calloc(5, sizeof(int));
// Check if memory allocation was successful
if (pointer == NULL) {
perror("Memory allocation failed");
return 1;
}
// Assign values to the allocated memory
for (int k = 0; k < 5; ++k) {
pointer[k] = k * 5;
}
// Display the values stored in the array
for (int j = 0; j < 5; ++j) {
printf("pointer[%d] = %d\n", j, pointer[j]); // Print each element of the array
}
// Deallocate the allocated memory using free
free(pointer);
return 0;
}
Output:
An array of five integers is allocated memory in this C program using calloc. It sets values to the array elements to avoid memory leaks, displays them, and then deallocates the memory. If memory allocation is unsuccessful, it prints a message with an error code and terminates.
Memory Deallocation in C++
Allocating and releasing memory when it's no longer needed are essential components of proper memory management, which aims to prevent memory leaks.
- Using the delete operator for deallocation:
Memory that has already been allocated using the new operator in C++ can be deallocated using the delete operator. It is essential for managing dynamic memory since it guards against memory leaks and releases resources quickly.
Syntax:
delete pointerVariable;
pointerVariable: A pointer to the memory that new used to allocate.
Code:
#include <iostream>
using namespace std;
int main() {
//pointer initialization to null
int* pointer = nullptr;
// Request memory for an integer variable using a new operator
pointer = new (nothrow) int;
if (!pointer) {
cout << "Memory allocation failed for 'pointer'\n";
} else {
// Store a value at the allocated address
*pointer = 10;
cout << "The value stored in 'pointer' is: " << *pointer << endl;
}
// Request memory for a float variable using new operator
float* request = new float(245.87);
Cout << "The value stored in 'request' is: " << *request << endl;
// Request memory for an array of integers using a new operator
int num = 5;
int* value = new (nothrow) int[num];
if (!value) {
cout << "Memory allocation failed for 'value'\n";
} else {
for (int i = 0; i < num; i++)
value[i] = i + 1;
cout << "Values stored in a block of memory: ";
for (int i = 0; i < num; i++)
cout << value[i] << " ";
}
// Free the allocated memory
delete pointer;
delete request;
// Free the block of allocated memory
delete[] value;
return 0;
}
Output:
Dynamic memory allocation and deallocation are demonstrated in the above C++ code. The new operator is used in this code to dynamically allocate memory for various data types, including integers, floats, and arrays of integers. To gracefully handle memory allocation errors without resulting in program crashes, the (nothrow) qualifier is employed. The code also demonstrates the significance of verifying memory allocation success before using the memory that has been allocated.
Values are initially stored in the assigned memory regions, after which they are retrieved and printed to the terminal. When allocated memory has completed its task, it is properly deallocated using the delete and delete[] operators to prevent memory leaks.
Advantages
- Effective Memory Use: Because of dynamic memory allocation, you can only allocate memory when needed. When the size of data structures is unknown at compilation time, this can be more memory-efficient than declaring all variables statically.
- Reduced Memory Wastage: Compared to fixed-size data structures, dynamic memory allocation allows you to allocate the right amount of memory for a given task.
- Data Sharing: By permitting data sharing across various program components, many components can access and modify the same data as necessary.
- Error Handling: The new operator in C++ lets you use Nothrow to manage memory allocation errors gently. Which reduces the likelihood of program crashes brought on by memory issues.
- Containers and Polymorphism: Using different container classes, such as vectors and linked lists, and working with polymorphic objects (objects of derived classes accessed through base class pointers), dynamic memory allocation is crucial.
- Reduced Stack Overflows: You can lower the risk of stack overflows by allocating huge objects on the heap (dynamically), especially when using recursive algorithms or working with enormous amounts of data.
Disadvantages
- Memory Leaks: Memory leaks are among the most prevalent problems. Memory that is no longer required can stay allocated if the delete and delete[] operators are not used appropriately, which leads to a steady reduction in available memory and potential program crashes.
- Dangling pointers: After memory is deallocated, the pointers pointing to it are known as "dangling pointers." Using these pointers to access or alter memory can lead to unpredictable behaviour, crashes, or security flaws.
- Fragmentation: Fragmentation can make allocation more difficult and decrease memory efficiency.
- Complexity of resource management: Monitoring memory usage might take time.
- Potential for Errors: Subtle problems can be caused by poor memory management.
- Lacks Automatic Garbage Collection: Automatic garbage collection must be in C++.
In C++, dynamic memory allocation and deallocation give users flexibility and control over memory resources, enabling effective memory usage and data size adaptation. They must be managed carefully to avoid problems like memory leaks, fragmentation, and dangling pointers. To ensure reliable and effective memory management, developers must adhere to best practices, use contemporary C++ features like smart pointers, and thoroughly test their code.