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 = 1000Pointer 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 1000Notice: 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
// /
// /
// p2Both 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 = 5Null 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 100Example 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.