Dynamic Memory Allocation
Dynamic memory allocation in C++ refers to the process of allocating memory at runtime using pointers. Unlike static memory allocation, which occurs at compile-time, dynamic memory allocation allows a program to request memory while it is running, making it more flexible and efficient for managing resources, especially when the amount of memory needed isn't known in advance.
C++ provides several operators and functions to handle dynamic memory allocation:
1. Using new and delete Operators
The new operator is used to allocate memory dynamically, and the delete operator is used to deallocate memory that was previously allocated with new.
a. Allocating Single Variables
You can allocate a single variable dynamically using the new operator.
int* ptr = new int; // Dynamically allocate memory for an int
*ptr = 10; // Assign a value to the dynamically allocated memory
cout << "Value: " << *ptr << endl; // Output: 10
delete ptr; // Deallocate the memory
ptr = nullptr; // Reset the pointer to avoid dangling pointers
- new int allocates memory for a single int.
- delete ptr deallocates the memory that ptr points to.
b. Allocating Arrays
You can also allocate memory for arrays dynamically using new[].
int* arr = new int[5]; // Dynamically allocate memory for an array of 5 ints
for (int i = 0; i < 5; i++) {
arr[i] = i * 10;
}
for (int i = 0; i < 5; i++) {
cout << arr[i] << " "; // Output: 0 10 20 30 40
}
cout << endl;
delete[] arr; // Deallocate the array memory
arr = nullptr;
- new int[5] allocates memory for an array of 5 ints.
- delete[] arr deallocates the memory allocated for the array.
2. Handling Memory Allocation Failures
If the new operator fails to allocate the requested memory, it throws a std::bad_alloc exception. You can handle this using a try-catch block:
try {
int* ptr = new int[10000000000]; // Attempt to allocate a large array
} catch (bad_alloc& e) {
cout << "Memory allocation failed: " << e.what() << endl;
}
If the allocation fails, the exception is caught, and the program can handle the error gracefully.
3. Customizing new and delete
C++ allows you to overload the new and delete operators to customize memory allocation behavior. This is particularly useful in scenarios where you need to manage memory differently, such as in embedded systems or for performance optimization.
Example:
void* operator new(size_t size) {
cout << "Custom new called. Size: " << size << endl;
void* p = malloc(size);
if (!p) throw bad_alloc();
return p;
}
void operator delete(void* p) {
cout << "Custom delete called." << endl;
free(p);
}
In this example, the global new and delete operators are overloaded to include custom behavior. Whenever memory is allocated or deallocated, the custom behavior is executed.
4. Using std::unique_ptr and std::shared_ptr
C++11 introduced smart pointers, which are objects that automatically manage the lifetime of dynamically allocated memory. The two most commonly used smart pointers are std::unique_ptr and std::shared_ptr.
a. std::unique_ptr
A std::unique_ptr is a smart pointer that owns a dynamically allocated object. It ensures that the object is deleted when the std::unique_ptr goes out of scope.
#include <iostream>
#include <memory>
using namespace std;
int main() {
unique_ptr<int> ptr(new int(10)); // Allocate memory and assign to unique_ptr
cout << "Value: " << *ptr << endl; // Output: 10
return 0;
} // Memory is automatically deallocated when ptr goes out of scope
b. std::shared_ptr
A std::shared_ptr is a smart pointer that maintains a reference count to manage shared ownership of a dynamically allocated object. The object is deleted when the last std::shared_ptr pointing to it is destroyed
#include <iostream>
#include <memory>
using namespace std;
int main() {
shared_ptr<int> ptr1(new int(20));
shared_ptr<int> ptr2 = ptr1; // ptr2 now shares ownership of the memory
cout << "Value: " << *ptr1 << endl; // Output: 20
cout << "Value: " << *ptr2 << endl; // Output: 20
return 0;
} // Memory is automatically deallocated when the last shared_ptr goes out of scope
5. Allocating Multi-Dimensional Arrays
Dynamic memory allocation can be used for multi-dimensional arrays. This is typically done using pointers to pointers.
Example:
#include <iostream>
using namespace std;
int main() {
int rows = 3, cols = 4;
// Allocate memory for an array of pointers
int** matrix = new int*[rows];
// Allocate memory for each row
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Assign values to the 2D array
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
}
}
// Print the 2D array
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}
// Deallocate memory
for (int i = 0; i < rows; i++) {
delete[] matrix[i];
}
delete[] matrix;
return 0;
}
In this example:
- int** matrix = new int*[rows];: Allocates memory for an array of pointers.
- matrix[i] = new int[cols];: Allocates memory for each row.
- The memory is deallocated in reverse order to avoid memory leaks.
6. Common Pitfalls
- Memory Leaks: Failing to deallocate memory with delete or delete[] can lead to memory leaks, where memory is no longer in use but has not been returned to the system.
- Dangling Pointers: Deleting a pointer and then attempting to use it leads to undefined behavior. After deletion, it's good practice to set the pointer to nullptr.
- Double Deletion: Deleting the same pointer twice can cause a program to crash. Always ensure that memory is only deleted once.
Conclusion
Dynamic memory allocation in C++ is a powerful feature that provides flexibility in managing memory. Understanding how to use new and delete correctly is essential for effective memory management. Smart pointers, introduced in C++11, offer an even safer and more convenient way to handle dynamic memory, reducing the risk of memory leaks and dangling pointers. By mastering dynamic memory allocation, you can write more efficient and robust C++ programs that can handle complex and variable memory needs.