Memory Management
Memory Layout
Understanding how C++ manages memory is crucial for writing efficient programs. Memory is divided into different segments:
Stack
Local variables, function parameters, return addresses
Heap
Dynamically allocated memory (new/delete)
Data Segment
Global and static variables
Code Segment
Compiled program instructions
Memory Allocation Example
#include <iostream>
using namespace std;
int globalVar = 100; // Data segment
static int staticVar = 200; // Data segment
void demonstrateMemory() {
int localVar = 300; // Stack
static int staticLocal = 400; // Data segment
int* heapVar = new int(500); // Heap
cout << "Global variable address: " << &globalVar << endl;
cout << "Static variable address: " << &staticVar << endl;
cout << "Local variable address: " << &localVar << endl;
cout << "Static local address: " << &staticLocal << endl;
cout << "Heap variable address: " << heapVar << endl;
delete heapVar; // Clean up heap memory
}
int main() {
demonstrateMemory();
return 0;
}Smart Pointers (C++11)
Smart pointers automatically manage memory and help prevent memory leaks:
Smart Pointers Example
#include <iostream>
#include <memory>
using namespace std;
class Resource {
public:
Resource(int id) : id(id) {
cout << "Resource " << id << " created" << endl;
}
~Resource() {
cout << "Resource " << id << " destroyed" << endl;
}
void use() {
cout << "Using resource " << id << endl;
}
private:
int id;
};
int main() {
cout << "=== unique_ptr ===" << endl;
{
unique_ptr<Resource> ptr1 = make_unique<Resource>(1);
ptr1->use();
// Transfer ownership
unique_ptr<Resource> ptr2 = move(ptr1);
if (!ptr1) {
cout << "ptr1 is now null" << endl;
}
ptr2->use();
} // ptr2 automatically deletes the resource
cout << "\n=== shared_ptr ===" << endl;
{
shared_ptr<Resource> ptr1 = make_shared<Resource>(2);
cout << "Reference count: " << ptr1.use_count() << endl;
{
shared_ptr<Resource> ptr2 = ptr1; // Share ownership
cout << "Reference count: " << ptr1.use_count() << endl;
ptr2->use();
} // ptr2 goes out of scope
cout << "Reference count: " << ptr1.use_count() << endl;
ptr1->use();
} // ptr1 goes out of scope, resource is deleted
cout << "\n=== weak_ptr ===" << endl;
{
shared_ptr<Resource> shared = make_shared<Resource>(3);
weak_ptr<Resource> weak = shared;
cout << "Weak pointer expired: " << weak.expired() << endl;
if (auto locked = weak.lock()) {
locked->use();
}
shared.reset(); // Release shared ownership
cout << "Weak pointer expired: " << weak.expired() << endl;
}
return 0;
}RAII Principle
Resource Acquisition Is Initialization - acquire resources in constructors and release them in destructors:
RAII Example
#include <iostream>
#include <fstream>
#include <vector>
using namespace std;
class FileManager {
private:
ofstream file;
public:
FileManager(const string& filename) : file(filename) {
if (!file.is_open()) {
throw runtime_error("Could not open file");
}
cout << "File opened: " << filename << endl;
}
~FileManager() {
if (file.is_open()) {
file.close();
cout << "File closed automatically" << endl;
}
}
void write(const string& data) {
file << data << endl;
}
};
class ArrayManager {
private:
int* data;
size_t size;
public:
ArrayManager(size_t s) : size(s) {
data = new int[size];
cout << "Array of size " << size << " allocated" << endl;
}
~ArrayManager() {
delete[] data;
cout << "Array deallocated" << endl;
}
// Copy constructor (Rule of Three)
ArrayManager(const ArrayManager& other) : size(other.size) {
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = other.data[i];
}
}
// Assignment operator
ArrayManager& operator=(const ArrayManager& other) {
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
for (size_t i = 0; i < size; ++i) {
data[i] = other.data[i];
}
}
return *this;
}
int& operator[](size_t index) {
return data[index];
}
};
int main() {
cout << "=== RAII with File ===" << endl;
try {
FileManager fm("example.txt");
fm.write("Hello, RAII!");
fm.write("Automatic cleanup!");
// File automatically closed when fm goes out of scope
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
}
cout << "\n=== RAII with Array ===" << endl;
{
ArrayManager arr(5);
for (int i = 0; i < 5; ++i) {
arr[i] = i * 10;
}
ArrayManager arr2 = arr; // Copy constructor
// Both arrays automatically cleaned up
}
return 0;
}Memory Management Best Practices
Prefer Stack Allocation
Use automatic storage duration when possible
Use Smart Pointers
Prefer smart pointers over raw pointers for dynamic allocation
Follow RAII
Tie resource lifetime to object lifetime
Rule of Three/Five
If you need destructor, copy constructor, or assignment operator, you likely need all
Initialize Pointers
Always initialize pointers to nullptr or valid addresses
Check for Null
Always check pointers before dereferencing