SrcForge

Lesson 21

Memory Diagrams for Pointers

Covers how variables, addresses, and pointers are stored in memory using diagrams, including dereferencing, multiple pointers, pointer to pointer, null pointers, and dynamic memory.

Lesson content

What are Memory Diagrams for Pointers?

A memory diagram is a visual representation of how variables and pointers are stored in a computer's memory. It shows variables, memory addresses, pointer variables, and the relationships between them. Instead of imagining memory, you can actually "see" where each variable lives.

Why do they exist?

Pointers are often difficult because they involve memory addresses, which are invisible while writing code. Memory diagrams solve this problem by showing where data is stored, what a pointer contains, which variable a pointer points to, and how changing data through pointers affects memory.

Real-world use cases

  • Dynamic memory allocation (new and delete)
  • Linked lists
  • Trees
  • Graphs
  • Smart pointers
  • Passing pointers to functions
  • Debugging segmentation faults

Prerequisites

  • Variables
  • Data types
  • Basic C++ syntax
  • Functions
  • Basic input/output
  • Operators
  • The address-of operator (&)
  • The dereference operator (*)

Variables occupy memory

Every variable is stored somewhere in memory.

int x = 10;

// Example memory:
// Address      Value
// --------     -----
// 1000         10
  • variable = x
  • value = 10
  • address = 1000 (example)

Memory address

Every variable has an address. The & operator returns the memory address of a variable.

int x = 10;

// x
// Address      Value
// 1000         10

// &x = 1000

Pointer variable

A pointer stores an address.

int x = 10;
int* ptr = &x;

// Memory:
// Address      Variable      Value
// --------------------------------
// 1000         x             10
// 2000         ptr           1000

// Diagram:
// ptr
//  |
//  | stores 1000
//  V
// +---------+
// | x = 10  |
// +---------+

Dereferencing

Using * accesses the value stored at the address.

*ptr

// means:
// Go to address 1000
// Read value
// 10

// Diagram:
// ptr
//  |
//  V
// Address 1000
// +--------+
// |   10   |
// +--------+

Changing value through pointer

*ptr = 50;

// Memory changes to:
// Address      Variable      Value
// --------------------------------
// 1000         x             50
// 2000         ptr           1000

Notice: the pointer never changed. Only the value at address 1000 changed.

Multiple pointers

int x = 25;

int* p1 = &x;

int* p2 = &x;

// Diagram:
//        p1
//         \
//          \
//           \
//            -----> x = 25
//           /
//          /
//        p2

Both pointers point to the same variable.

Pointer to pointer

int x = 5;

int* p = &x;

int** pp = &p;

// Memory:
// Address      Variable      Value
// --------------------------------
// 1000         x             5
// 2000         p             1000
// 3000         pp            2000

// Diagram:
// pp
//  |
//  V
//  p
//  |
//  V
//  x = 5

Null pointer

int* ptr = nullptr;

// Diagram:
// ptr
//  |
// nullptr
// (no memory location)

A null pointer points to nothing.

Dynamic memory

int* ptr = new int(100);

// Diagram:
// Stack
// ptr
//  |
//  V
// Heap
// +------+
// | 100  |
// +------+

// Memory:
// Stack
// Address      Variable
// ---------------------
// 1000         ptr
// Value = 5000

// Heap
// Address      Value
// ------------------
// 5000         100

Example 1 – Beginner: Basic pointer

#include <iostream>
using namespace std;

int main()
{
    int number = 25;

    int* ptr = &number;

    cout << number << endl;

    cout << &number << endl;

    cout << ptr << endl;

    cout << *ptr << endl;

    return 0;
}

Memory diagram: ptr points down to number = 25.

Example 2 – Intermediate: Modify through pointer

#include <iostream>
using namespace std;

int main()
{
    int value = 10;

    int* ptr = &value;

    *ptr = 99;

    cout << value << endl;

    return 0;
}
  • Before: value = 10
  • After: value = 99

Example 3 – Intermediate: Two pointers

#include <iostream>
using namespace std;

int main()
{
    int score = 80;

    int* p1 = &score;

    int* p2 = &score;

    *p2 = 100;

    cout << *p1 << endl;

    return 0;
}

Diagram: both p1 and p2 point to the same score, which is now 100.

Example 4 – Advanced: Dynamic memory allocation

#include <iostream>
using namespace std;

int main()
{
    int* ptr = new int(500);

    cout << *ptr << endl;

    delete ptr;

    ptr = nullptr;

    return 0;
}

Diagram: ptr on the stack points to 500 on the heap.

Common mistakes and pitfalls

  • Dereferencing an uninitialized pointer (int* p; *p = 5;). Fix: int x = 5; int* p = &x;
  • Using a pointer after delete (delete p; *p = 10;). Fix: delete p; p = nullptr;
  • Dereferencing a null pointer (int* p = nullptr; *p = 10;). Fix: check if (p != nullptr) before dereferencing
  • Forgetting delete after new. Fix: pair every new with a delete, or prefer smart pointers
  • Assuming a pointer stores the value. Fix: remember a pointer stores an address, not the data itself

Best practices

  • Draw memory diagrams when learning or debugging pointer code
  • Initialize pointers with nullptr if they don't yet point to valid memory
  • Prefer references when ownership or reseating isn't needed
  • Use smart pointers (unique_ptr, shared_ptr) instead of raw pointers for owning dynamically allocated memory
  • Delete dynamically allocated memory exactly once, or rely on RAII to manage it automatically
  • Use descriptive variable names such as head, current, or buffer to make pointer relationships clearer

When not to use this

  • Writing simple programs that don't use pointers
  • Using high-level abstractions where memory management is handled automatically
  • Working exclusively with references and standard library containers without needing to reason about addresses
  • Needing precise runtime memory layouts, since actual addresses vary between program executions

Summary

  • Memory diagrams visualize how variables and pointers are stored
  • Every variable occupies a memory location with a unique address
  • A pointer stores the address of another variable
  • The & operator retrieves a variable's address
  • The * operator dereferences a pointer to access or modify the value
  • Multiple pointers can refer to the same memory location
  • Pointer-to-pointer variables store the address of another pointer
  • nullptr represents a pointer that points to nothing
  • Dynamically allocated memory lives on the heap, while local variables usually live on the stack
  • Drawing memory diagrams makes debugging pointer-related code much easier

Frequently asked questions

Why do pointer diagrams use fake memory addresses?

Actual memory addresses change each time a program runs. Diagrams use simple example addresses (like 1000 or 0x1000) to make the relationships easy to understand.

Does a pointer store a value or an address?

A pointer stores an address. To access the value at that address, you use the dereference operator (*).

Can two pointers point to the same variable?

Yes. Multiple pointers can store the same address, allowing them to access and modify the same underlying variable.

Why should I set a pointer to nullptr after delete?

After delete, the pointer still contains the old address, which is no longer valid. Setting it to nullptr helps prevent accidental use of a dangling pointer.

Are memory diagrams required in professional C++ development?

No, but they are an excellent learning and debugging aid. Many experienced developers sketch quick memory diagrams when investigating complex pointer bugs or designing data structures.