cpp
Dynamic Memory
03_Dynamic_Memory⚙️cpp
/**
* ============================================================
* C++ DYNAMIC MEMORY ALLOCATION
* ============================================================
*
* This file covers:
* - Stack vs Heap memory
* - new and delete operators
* - Dynamic arrays
* - Memory leaks
* - Common pitfalls
*
* Compile: g++ -std=c++17 -Wall 01_dynamic_memory.cpp -o dynamic_memory
* Run: ./dynamic_memory
*
* ============================================================
*/
#include <iostream>
#include <cstring>
using namespace std;
// Simple class for demonstration
class Demo {
public:
int value;
Demo() : value(0) {
cout << " Demo default constructor" << endl;
}
Demo(int v) : value(v) {
cout << " Demo constructor with value " << v << endl;
}
~Demo() {
cout << " Demo destructor, value was " << value << endl;
}
};
int main() {
cout << "============================================" << endl;
cout << " C++ DYNAMIC MEMORY ALLOCATION" << endl;
cout << "============================================" << endl << endl;
// ========================================================
// PART 1: STACK VS HEAP
// ========================================================
cout << "--- PART 1: STACK VS HEAP ---" << endl << endl;
/*
* STACK MEMORY:
* - Automatic allocation/deallocation
* - Fast access
* - Limited size
* - Variables destroyed when scope ends
*
* HEAP MEMORY:
* - Manual allocation with new
* - Manual deallocation with delete
* - Larger available space
* - Persists until explicitly freed
*/
// Stack allocation
int stackVar = 10; // On stack, automatic cleanup
// Heap allocation
int* heapVar = new int(20); // On heap, manual cleanup needed
cout << "Stack variable: " << stackVar << endl;
cout << "Heap variable: " << *heapVar << endl;
delete heapVar; // Must free heap memory!
heapVar = nullptr; // Good practice: set to nullptr after delete
cout << "\nStack vs Heap Comparison:" << endl;
cout << "┌─────────────────┬─────────────────┬─────────────────┐" << endl;
cout << "│ Feature │ Stack │ Heap │" << endl;
cout << "├─────────────────┼─────────────────┼─────────────────┤" << endl;
cout << "│ Allocation │ Automatic │ Manual (new) │" << endl;
cout << "│ Deallocation │ Automatic │ Manual (delete) │" << endl;
cout << "│ Speed │ Fast │ Slower │" << endl;
cout << "│ Size │ Limited (~MB) │ Large (~GB) │" << endl;
cout << "│ Fragmentation │ No │ Possible │" << endl;
cout << "│ Scope │ Function/block │ Until deleted │" << endl;
cout << "└─────────────────┴─────────────────┴─────────────────┘" << endl;
cout << endl;
// ========================================================
// PART 2: NEW AND DELETE FOR SINGLE OBJECTS
// ========================================================
cout << "--- PART 2: NEW AND DELETE ---" << endl << endl;
// Allocate single int
int* pInt = new int; // Uninitialized
*pInt = 42;
cout << "new int: " << *pInt << endl;
delete pInt;
pInt = nullptr;
// Allocate with initialization
int* pInitInt = new int(100); // Direct initialization
cout << "new int(100): " << *pInitInt << endl;
delete pInitInt;
pInitInt = nullptr;
// Allocate with braces (C++11)
int* pBraceInt = new int{200}; // Brace initialization
cout << "new int{200}: " << *pBraceInt << endl;
delete pBraceInt;
// Allocate object
cout << "\nAllocating Demo object:" << endl;
Demo* pDemo = new Demo(42);
cout << "pDemo->value = " << pDemo->value << endl;
delete pDemo; // Calls destructor
cout << endl;
// ========================================================
// PART 3: DYNAMIC ARRAYS
// ========================================================
cout << "--- PART 3: DYNAMIC ARRAYS ---" << endl << endl;
// Allocate array of 5 integers
int* arr = new int[5];
// Initialize array
for (int i = 0; i < 5; i++) {
arr[i] = (i + 1) * 10;
}
// Access array
cout << "Dynamic array: ";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
// IMPORTANT: Use delete[] for arrays!
delete[] arr; // Not just delete!
arr = nullptr;
// Array with initialization (C++11)
int* arr2 = new int[5]{1, 2, 3, 4, 5};
cout << "Initialized array: ";
for (int i = 0; i < 5; i++) {
cout << arr2[i] << " ";
}
cout << endl;
delete[] arr2;
// Array of objects
cout << "\nAllocating array of 3 Demo objects:" << endl;
Demo* demos = new Demo[3]; // Calls default constructor 3 times
cout << "Deleting array of Demo objects:" << endl;
delete[] demos; // Calls destructor 3 times
cout << endl;
// ========================================================
// PART 4: 2D DYNAMIC ARRAYS
// ========================================================
cout << "--- PART 4: 2D DYNAMIC ARRAYS ---" << endl << endl;
int rows = 3, cols = 4;
// Method 1: Array of pointers
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++) {
matrix[i] = new int[cols];
}
// Initialize
int count = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
matrix[i][j] = count++;
}
}
// Display
cout << "2D Dynamic Array (" << rows << "x" << cols << "):" << endl;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
cout << matrix[i][j] << "\t";
}
cout << endl;
}
// Deallocate (reverse order!)
for (int i = 0; i < rows; i++) {
delete[] matrix[i]; // Delete each row first
}
delete[] matrix; // Then delete the array of pointers
cout << "\n💡 For 2D arrays, consider using vector<vector<int>>" << endl;
cout << endl;
// ========================================================
// PART 5: MEMORY LEAKS
// ========================================================
cout << "--- PART 5: MEMORY LEAKS ---" << endl << endl;
cout << "Common causes of memory leaks:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "\n1. Forgetting to delete:" << endl;
cout << " int* p = new int; // Allocated" << endl;
cout << " // forgot delete p; → LEAK!" << endl;
cout << "\n2. Overwriting pointer:" << endl;
cout << " int* p = new int(10);" << endl;
cout << " p = new int(20); // Lost reference to first int!" << endl;
cout << "\n3. Returning without cleanup:" << endl;
cout << " int* p = new int;" << endl;
cout << " if (error) return; // Forgot to delete!" << endl;
cout << "\n4. Exception thrown before delete:" << endl;
cout << " int* p = new int;" << endl;
cout << " mayThrow(); // If exception, p not deleted!" << endl;
cout << " delete p;" << endl;
cout << "\n5. Using delete instead of delete[]:" << endl;
cout << " int* arr = new int[100];" << endl;
cout << " delete arr; // WRONG! Use delete[] arr;" << endl;
cout << endl;
// ========================================================
// PART 6: COMMON PITFALLS
// ========================================================
cout << "--- PART 6: COMMON PITFALLS ---" << endl << endl;
// Double delete
cout << "1. Double Delete (DANGEROUS!):" << endl;
cout << " int* p = new int;" << endl;
cout << " delete p;" << endl;
cout << " delete p; // UNDEFINED BEHAVIOR!" << endl;
cout << " Solution: p = nullptr after delete" << endl;
// Dangling pointer
cout << "\n2. Dangling Pointer:" << endl;
cout << " int* p = new int(10);" << endl;
cout << " delete p;" << endl;
cout << " *p = 20; // UNDEFINED BEHAVIOR!" << endl;
cout << " Solution: Set p = nullptr after delete" << endl;
// Using uninitialized pointer
cout << "\n3. Using Uninitialized Pointer:" << endl;
cout << " int* p; // Not initialized!" << endl;
cout << " *p = 10; // UNDEFINED BEHAVIOR!" << endl;
cout << " Solution: int* p = nullptr;" << endl;
// Array bounds
cout << "\n4. Array Out of Bounds:" << endl;
cout << " int* arr = new int[5];" << endl;
cout << " arr[10] = 100; // UNDEFINED BEHAVIOR!" << endl;
cout << " Solution: Always track array size" << endl;
cout << endl;
// ========================================================
// PART 7: NOTHROW NEW
// ========================================================
cout << "--- PART 7: ALLOCATION FAILURE ---" << endl << endl;
// Regular new throws std::bad_alloc on failure
try {
// This would fail with very large size
// int* huge = new int[999999999999];
cout << "Regular new throws std::bad_alloc on failure" << endl;
} catch (bad_alloc& e) {
cout << "Allocation failed: " << e.what() << endl;
}
// nothrow new returns nullptr on failure
int* safePtr = new(nothrow) int[100];
if (safePtr == nullptr) {
cout << "Allocation failed (nothrow)" << endl;
} else {
cout << "nothrow new returns nullptr on failure" << endl;
delete[] safePtr;
}
cout << endl;
// ========================================================
// PART 8: DYNAMIC MEMORY BEST PRACTICES
// ========================================================
cout << "--- PART 8: BEST PRACTICES ---" << endl << endl;
cout << "✓ Prefer smart pointers over raw pointers" << endl;
cout << "✓ Use std::vector instead of dynamic arrays" << endl;
cout << "✓ Set pointers to nullptr after delete" << endl;
cout << "✓ Match new with delete, new[] with delete[]" << endl;
cout << "✓ Use RAII (Resource Acquisition Is Initialization)" << endl;
cout << "✓ Check for nullptr before using pointers" << endl;
cout << "✓ Document ownership of dynamic memory" << endl;
cout << "✓ Use tools like Valgrind to detect leaks" << endl;
cout << "\n⚠️ Modern C++ Recommendation:" << endl;
cout << " Use std::unique_ptr and std::shared_ptr" << endl;
cout << " (covered in Smart Pointers section)" << endl;
cout << endl;
cout << "============================================" << endl;
cout << "DYNAMIC MEMORY SUMMARY:" << endl;
cout << "============================================" << endl;
cout << "• new: allocates on heap" << endl;
cout << "• delete: frees single object" << endl;
cout << "• delete[]: frees array" << endl;
cout << "• Always match new with delete" << endl;
cout << "• Set nullptr after delete" << endl;
cout << "• Prefer smart pointers!" << endl;
cout << "============================================" << endl;
return 0;
}
// ============================================================
// EXERCISES:
// ============================================================
/*
* 1. Create a function that dynamically allocates an array,
* fills it with random numbers, and returns the pointer.
* Remember to document who is responsible for deletion!
*
* 2. Create a dynamic string class that:
* - Uses new[] to allocate char array
* - Implements destructor to free memory
* - Implements append() function that reallocates
*
* 3. Create a 2D matrix class with dynamic allocation:
* - Constructor takes rows and cols
* - Implements operator()(row, col) for access
* - Properly frees memory in destructor
*
* 4. Implement a simple memory pool allocator
*
* 5. Use Valgrind (if available) to check for memory leaks
* in a program with intentional leaks
*/