READMEC++

README

Pointers Memory / Dynamic Memory

Concept Lesson
Intermediate
4 min

Learning Objective

Understand Pointers Memory well enough to explain it, recognize it in C++, and apply it in a small task.

Why It Matters

This concept is part of the foundation that later lessons and projects assume you already understand.

PointersTable Of ContentsStack Vs HeapStack MemoryHeap Memory
Private notes
0/8000

Notes stay private to your browser until account sync is configured.

README
2 min read18 headings

Dynamic Memory in C++

Table of Contents

  1. Stack vs Heap
  2. new and delete
  3. Dynamic Arrays
  4. Memory Management Issues
  5. RAII Principle
  6. Best Practices

Stack vs Heap

Stack Memory

  • Automatic allocation by compiler
  • Fast - just moves stack pointer
  • Limited size (usually 1-8 MB)
  • Automatic cleanup when scope ends
  • LIFO (Last In, First Out)
void stackExample() {
    int x = 10;           // Stack allocated
    double arr[100];      // Stack allocated array
    string name = "Hi";   // Stack (string object; data may be heap)
}  // All automatically destroyed here

Heap Memory

  • Manual allocation with new/delete
  • Slower - involves memory manager
  • Large - limited by available RAM
  • Manual cleanup required
  • Flexible - allocate any size at runtime
void heapExample() {
    int* ptr = new int(10);        // Heap allocated
    double* arr = new double[100];  // Heap allocated array

    // Must manually deallocate
    delete ptr;
    delete[] arr;
}

Memory Layout

High Address
┌─────────────────┐
│     Stack       │  ↓ Grows down
│  (local vars)   │
├─────────────────┤
│        ↓        │
│                 │
│        ↑        │
├─────────────────┤
│      Heap       │  ↑ Grows up
│ (dynamic alloc) │
├─────────────────┤
│   BSS/Data      │  (global/static)
├─────────────────┤
│      Code       │  (program)
└─────────────────┘
Low Address

new and delete

Allocating Single Objects

// Allocate integer
int* intPtr = new int;        // Uninitialized
int* intPtr2 = new int(42);   // Initialized to 42
int* intPtr3 = new int{42};   // C++11 uniform init

// Allocate object
string* strPtr = new string("Hello");
MyClass* objPtr = new MyClass(arg1, arg2);

// Don't forget to delete!
delete intPtr;
delete intPtr2;
delete intPtr3;
delete strPtr;
delete objPtr;

What new Does

  1. Allocates memory from heap
  2. Calls constructor (for objects)
  3. Returns pointer to allocated memory

What delete Does

  1. Calls destructor (for objects)
  2. Returns memory to heap

Handling Allocation Failure

// By default, new throws std::bad_alloc on failure
try {
    int* huge = new int[1000000000000];
} catch (const bad_alloc& e) {
    cout << "Allocation failed: " << e.what() << endl;
}

// nothrow version returns nullptr on failure
int* ptr = new(nothrow) int[1000000000000];
if (ptr == nullptr) {
    cout << "Allocation failed" << endl;
}

Dynamic Arrays

Allocation and Deallocation

// Allocate array
int* arr = new int[10];          // 10 uninitialized ints
int* arr2 = new int[10]();       // 10 zero-initialized ints
int* arr3 = new int[5]{1,2,3,4,5};  // Initialized with values

// Access elements normally
arr[0] = 100;
for (int i = 0; i < 10; i++) {
    arr[i] = i * 10;
}

// MUST use delete[] for arrays!
delete[] arr;
delete[] arr2;
delete[] arr3;

delete vs delete[]

int* single = new int(42);
int* array = new int[10];

delete single;    // OK: single object
delete[] array;   // OK: array

// delete array;  // WRONG! Undefined behavior
// delete[] single;  // WRONG! Undefined behavior

Dynamic Size at Runtime

int size;
cout << "Enter size: ";
cin >> size;

int* dynamicArr = new int[size];  // Size determined at runtime

for (int i = 0; i < size; i++) {
    dynamicArr[i] = i * 2;
}

delete[] dynamicArr;

2D Dynamic Arrays

// Allocate 2D array (rows x cols)
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];
}

// Use it
matrix[1][2] = 42;

// Deallocate (reverse order!)
for (int i = 0; i < rows; i++) {
    delete[] matrix[i];
}
delete[] matrix;

// Method 2: Single contiguous block (better cache performance)
int* flat = new int[rows * cols];
// Access: flat[row * cols + col]
flat[1 * cols + 2] = 42;
delete[] flat;

Memory Management Issues

1. Memory Leak

Allocated memory that is never freed.

void memoryLeak() {
    int* ptr = new int(42);
    // Oops! Forgot to delete
}  // ptr goes out of scope, memory is lost forever

// Fix
void noLeak() {
    int* ptr = new int(42);
    // ... use ptr ...
    delete ptr;  // Don't forget!
}

2. Dangling Pointer

Pointer to memory that has been freed.

int* ptr = new int(42);
delete ptr;
// ptr is now dangling!
cout << *ptr;  // UNDEFINED BEHAVIOR

// Fix: Set to nullptr after delete
delete ptr;
ptr = nullptr;

3. Double Free

Freeing the same memory twice.

int* ptr = new int(42);
delete ptr;
delete ptr;  // UNDEFINED BEHAVIOR - crash likely!

// Fix: Set to nullptr after delete
delete ptr;
ptr = nullptr;
delete ptr;  // OK: deleting nullptr is safe

4. Use After Free

Using memory after it's been freed.

int* ptr = new int(42);
int* alias = ptr;
delete ptr;
cout << *alias;  // UNDEFINED BEHAVIOR - alias is dangling too!

5. Array/Non-Array Mismatch

int* arr = new int[10];
delete arr;    // WRONG! Should be delete[]

int* single = new int(42);
delete[] single;  // WRONG! Should be delete

6. Uninitialized Pointer

int* ptr;      // Uninitialized - contains garbage!
*ptr = 42;     // UNDEFINED BEHAVIOR - crash likely!

// Fix: Initialize to nullptr
int* ptr = nullptr;

RAII Principle

Resource Acquisition Is Initialization

Core idea: Tie resource lifetime to object lifetime.

Example: Manual vs RAII

// Manual (error-prone)
void manual() {
    int* data = new int[100];

    if (someCondition) {
        return;  // LEAK! Forgot to delete
    }

    process(data);  // Might throw exception - LEAK!

    delete[] data;
}

// RAII (safe)
void raii() {
    vector<int> data(100);  // RAII container

    if (someCondition) {
        return;  // OK! vector destructor frees memory
    }

    process(data);  // If throws, destructor still called
}  // vector destructor automatically called

Custom RAII Class

class IntArray {
private:
    int* data;
    int size;

public:
    IntArray(int n) : size(n) {
        data = new int[n];  // Acquire resource
    }

    ~IntArray() {
        delete[] data;  // Release resource
    }

    int& operator[](int i) { return data[i]; }

    // Prevent copying (Rule of Three issue)
    IntArray(const IntArray&) = delete;
    IntArray& operator=(const IntArray&) = delete;
};

void useRAII() {
    IntArray arr(100);
    arr[0] = 42;
}  // Destructor automatically frees memory

Best Practices

✅ Do

// 1. Use smart pointers instead of raw new/delete
#include <memory>
unique_ptr<int> ptr = make_unique<int>(42);
shared_ptr<int[]> arr = make_shared<int[]>(100);

// 2. Use containers instead of raw arrays
vector<int> arr(100);  // Automatic memory management

// 3. Initialize pointers
int* ptr = nullptr;

// 4. Set to nullptr after delete
delete ptr;
ptr = nullptr;

// 5. Match new with delete, new[] with delete[]
int* single = new int(42);
delete single;

int* array = new int[10];
delete[] array;

// 6. Check for null before using
if (ptr != nullptr) {
    *ptr = 10;
}

❌ Don't

// 1. Don't use raw new/delete when avoidable
int* ptr = new int(42);  // Prefer smart pointers

// 2. Don't forget to delete
void leak() {
    int* ptr = new int(42);
}  // Memory leaked!

// 3. Don't use uninitialized pointers
int* ptr;
*ptr = 42;  // Crash!

// 4. Don't double delete
delete ptr;
delete ptr;  // Crash!

// 5. Don't mismatch new/new[] with delete/delete[]
int* arr = new int[10];
delete arr;  // Wrong! Should be delete[]

// 6. Don't return pointer to local
int* bad() {
    int x = 10;
    return &x;  // Dangling!
}

Modern C++ Guidelines

  1. Prefer stack allocation when possible
  2. Use vector instead of new[]
  3. Use smart pointers for dynamic allocation
  4. Follow Rule of Zero/Three/Five for classes with resources
  5. Use RAII for all resource management

Quick Reference

// Single object
int* ptr = new int(42);
delete ptr;
ptr = nullptr;

// Array
int* arr = new int[10];
delete[] arr;
arr = nullptr;

// Zero-initialized array
int* arr = new int[10]();

// Initialized array (C++11)
int* arr = new int[3]{1, 2, 3};

// nothrow version
int* ptr = new(nothrow) int[size];
if (!ptr) { /* handle failure */ }

// 2D array
int** matrix = new int*[rows];
for (int i = 0; i < rows; i++)
    matrix[i] = new int[cols];

// Cleanup 2D
for (int i = 0; i < rows; i++)
    delete[] matrix[i];
delete[] matrix;

// Better alternatives
#include <memory>
unique_ptr<int> ptr = make_unique<int>(42);
unique_ptr<int[]> arr = make_unique<int[]>(10);
vector<int> vec(10);

Compile & Run

g++ -std=c++17 -Wall -Wextra examples.cpp -o examples
./examples

For memory debugging:

# With Valgrind (Linux)
valgrind --leak-check=full ./examples

# With Address Sanitizer
g++ -std=c++17 -fsanitize=address -g examples.cpp -o examples
./examples

Skill Check

Test this lesson

Answer 4 quick questions to lock in the lesson and feed your adaptive practice queue.

--
Score
0/4
Answered
Not attempted
Status
1

Which module does this lesson belong to?

2

Which section is covered in this lesson content?

3

Which term is most central to this lesson?

4

What is the best way to use this lesson for real learning?

Your answers save locally first, then sync when account storage is available.
Practice queue