C++ Dynamic Memory
Introduction
In C++, memory management is a critical aspect of programming that directly impacts your application's performance and reliability. While automatic variables (declared inside functions) are allocated on the stack and have a limited lifetime, dynamic memory allocation allows you to request memory from the heap during runtime, giving you more flexibility and control.
Dynamic memory allocation is essential when:
- You don't know how much memory you need at compile time
- You need to allocate large blocks of memory
- You need data to persist beyond the scope of the function where it's created
- You're building complex data structures like linked lists, trees, or graphs
In this tutorial, we'll explore how C++ handles dynamic memory through pointers and the new
/delete
operators.
Understanding Memory in C++
Before diving into dynamic memory, let's understand how memory is organized in C++:
- Stack: Fast, automatically managed memory with limited size
- Heap: Larger, manually managed memory for dynamic allocation
- Static/Global: Memory for global variables and static members
- Code Segment: Where your compiled code resides
In this tutorial, we'll focus on the heap memory (highlighted above).
Basic Dynamic Memory Allocation
The new
Operator
In C++, we use the new
operator to allocate memory dynamically. It returns a pointer to the allocated memory.
// Allocating a single integer
int* ptr = new int;
// Allocating and initializing
int* ptr2 = new int(42);
// Allocating an array of 10 integers
int* arr = new int[10];
The delete
Operator
When you're done with dynamically allocated memory, you must release it using the delete
operator.
// Deallocating a single variable
delete ptr;
// Deallocating an array
delete[] arr;
Example: Simple Dynamic Memory Allocation
#include <iostream>
using namespace std;
int main() {
// Allocate a single integer
int* number = new int;
// Assign a value to it
*number = 42;
// Use the dynamically allocated memory
cout << "Dynamically allocated number: " << *number << endl;
// Release the memory when done
delete number;
// Nullify the pointer after deletion (best practice)
number = nullptr;
return 0;
}
Output:
Dynamically allocated number: 42
Working with Dynamic Arrays
Dynamic arrays are useful when you need to determine array size at runtime.
Example: Dynamic Array
#include <iostream>
using namespace std;
int main() {
// Get the size of the array from the user
int size;
cout << "Enter the size of the array: ";
cin >> size;
// Allocate an array of the specified size
int* dynamicArray = new int[size];
// Fill the array with values
cout << "Enter " << size << " integers:" << endl;
for(int i = 0; i < size; i++) {
cin >> dynamicArray[i];
}
// Calculate the sum of elements
int sum = 0;
for(int i = 0; i < size; i++) {
sum += dynamicArray[i];
}
cout << "Sum of all elements: " << sum << endl;
// Release the memory
delete[] dynamicArray;
dynamicArray = nullptr;
return 0;
}
Sample Input/Output:
Enter the size of the array: 4
Enter 4 integers:
10 20 30 40
Sum of all elements: 100
Common Pitfalls in Dynamic Memory Management
1. Memory Leaks
Memory leaks occur when you allocate memory but forget to deallocate it.
void leakyFunction() {
int* data = new int[1000];
// Function ends without 'delete[] data'
// This memory is now leaked!
}
2. Dangling Pointers
Dangling pointers occur when you use a pointer after the memory it points to has been deallocated.
int* ptr = new int(42);
delete ptr; // Memory is deallocated
cout << *ptr; // DANGEROUS: Accessing deallocated memory
3. Double Deletion
Attempting to delete the same memory twice can cause runtime errors.
int* ptr = new int;
delete ptr;
delete ptr; // ERROR: Double deletion
4. Using Wrong Delete Form
Using delete
for arrays or delete[]
for single objects can cause undefined behavior.
int* single = new int;
int* array = new int[10];
delete[] single; // WRONG: Should use delete
delete array; // WRONG: Should use delete[]
Best Practices for Dynamic Memory
- Always match
new
withdelete
andnew[]
withdelete[]
- Set pointers to
nullptr
after deletion - Use smart pointers whenever possible (covered in advanced tutorials)
- Check if memory allocation was successful
- Keep track of all dynamic allocations
Example with Best Practices
#include <iostream>
using namespace std;
int main() {
// Allocate memory
int* data = nullptr; // Initialize to nullptr
try {
data = new int[100]; // Try to allocate
} catch (const bad_alloc& e) {
cerr << "Memory allocation failed: " << e.what() << endl;
return 1;
}
// Use the memory
for(int i = 0; i < 100; i++) {
data[i] = i * 2;
}
// Display some values
cout << "Some values from the array:" << endl;
for(int i = 0; i < 5; i++) {
cout << "data[" << i << "] = " << data[i] << endl;
}
// Clean up properly
delete[] data;
data = nullptr; // Set to nullptr after deletion
return 0;
}
Output:
Some values from the array:
data[0] = 0
data[1] = 2
data[2] = 4
data[3] = 6
data[4] = 8
Real-World Application: Dynamic String Builder
Let's implement a simple dynamic string builder that automatically grows as needed:
#include <iostream>
#include <cstring>
using namespace std;
class DynamicStringBuilder {
private:
char* buffer;
int capacity;
int length;
void resize(int newCapacity) {
char* newBuffer = new char[newCapacity];
// Copy existing content
for (int i = 0; i < length; i++) {
newBuffer[i] = buffer[i];
}
// Delete old buffer
delete[] buffer;
// Update to new buffer
buffer = newBuffer;
capacity = newCapacity;
}
public:
DynamicStringBuilder(int initialCapacity = 10) {
capacity = initialCapacity;
buffer = new char[capacity];
length = 0;
buffer[0] = '\0';
}
~DynamicStringBuilder() {
delete[] buffer;
}
void append(const char* str) {
int strLength = strlen(str);
// Check if we need to resize
if (length + strLength + 1 > capacity) {
resize(capacity * 2 + strLength);
}
// Append the string
for (int i = 0; i < strLength; i++) {
buffer[length + i] = str[i];
}
length += strLength;
buffer[length] = '\0';
}
const char* toString() const {
return buffer;
}
};
int main() {
DynamicStringBuilder builder;
builder.append("Hello, ");
builder.append("dynamic ");
builder.append("memory ");
builder.append("management!");
cout << "Built string: " << builder.toString() << endl;
return 0;
}
Output:
Built string: Hello, dynamic memory management!
This example demonstrates how dynamic memory allows you to build data structures that can grow as needed during runtime.
Common Allocation Patterns with Dynamic Memory
1. Dynamic 2D Arrays
// Create a 2D array of size rows × cols
int rows = 3;
int cols = 4;
// Allocate an array of pointers
int** matrix = new int*[rows];
// Allocate each row
for(int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Use the matrix
for(int i = 0; i < rows; i++) {
for(int j = 0; j < cols; j++) {
matrix[i][j] = i * cols + j;
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// Clean up - delete each row first
for(int i = 0; i < rows; i++) {
delete[] matrix[i];
}
// Then delete the array of pointers
delete[] matrix;
2. Dynamic Objects
class Person {
public:
string name;
int age;
Person(string n, int a) : name(n), age(a) {
cout << "Person " << name << " created." << endl;
}
~Person() {
cout << "Person " << name << " destroyed." << endl;
}
void introduce() {
cout << "Hi, I'm " << name << " and I'm " << age << " years old." << endl;
}
};
// Create a dynamic Person object
Person* person = new Person("Alice", 25);
person->introduce();
delete person; // Don't forget to delete!
Memory Management vs. Garbage Collection
Unlike languages with garbage collection (like Java or Python), C++ requires manual memory management. This gives you greater control but also more responsibility:
Manual Memory Management (C++) | Garbage Collection |
---|---|
Complete control over when memory is freed | Automatic memory recovery |
Potentially better performance | Potential performance pauses |
Risk of memory leaks, dangling pointers | Safer, fewer memory-related bugs |
Explicit release of resources | Resources may be held longer than needed |
Advanced Memory Management (Preview)
While this tutorial covers the basics, C++ offers more sophisticated tools for memory management:
- Smart Pointers:
unique_ptr
,shared_ptr
, andweak_ptr
that automatically manage memory - Placement new: Construct objects at specific memory addresses
- Custom allocators: Create specialized memory allocation strategies
- RAII (Resource Acquisition Is Initialization): Tie resource management to object lifetime
Summary
Dynamic memory allocation is a powerful feature in C++ that allows you to:
- Allocate memory at runtime
- Create data structures that can grow and shrink
- Manage memory resources explicitly
Key points to remember:
- Use
new
to allocate memory anddelete
to free it - Always match
new
withdelete
andnew[]
withdelete[]
- Set pointers to
nullptr
after deletion - Be aware of memory leaks, dangling pointers, and double deletion
- Follow best practices to prevent memory-related issues
Exercises
- Create a program that dynamically allocates an array based on user input, then finds the maximum and minimum values.
- Implement a simple linked list using dynamic memory allocation.
- Write a function that takes a string and returns a dynamically allocated copy with all vowels removed.
- Create a dynamic matrix multiplication program that allocates two 2D matrices of sizes determined at runtime.
- Implement a function that checks for memory leaks by tracking allocations and deallocations.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)