C++ • MEMORY AND POINTERS

Addresses & Pointers — Understand addresses and pointers conceptually

Grasp how memory addresses and pointer variables form the foundation of low-level data manipulation in C++.

Historical Context & Motivation

The concept of directly manipulating memory addresses predates high-level programming languages by decades. In the earliest days of computing, programmers wrote machine code that explicitly referenced physical locations in memory—every instruction required the programmer to know exactly where data lived. As computers grew in complexity, so did the need for abstractions that could reference memory locations without forcing programmers to track raw numeric addresses by hand. The evolution from assembly-language addressing modes to the typed pointer systems of languages like C and C++ represents one of the most consequential design decisions in the history of systems programming.

1945
Von Neumann Architecture
John von Neumann proposed the stored-program model, where both instructions and data reside in a linearly addressed memory. Every byte of memory receives a unique numeric address, establishing the conceptual foundation for pointers.
1964
PL/I Introduces Explicit Pointers
IBM's PL/I language introduced pointer variables as first-class objects, allowing programmers to store and pass around memory addresses rather than copying entire data structures.
1972
C Language and Typed Pointers
Dennis Ritchie's C language refined the pointer concept by associating each pointer with a type, enabling pointer arithmetic that scales by the size of the pointed-to object. This design directly influenced C++.
1979
C++ Emerges as 'C with Classes'
Bjarne Stroustrup began developing C++, inheriting C's pointer model while adding references, constructors, and destructors—features that rely heavily on address-level control of object lifetimes.
2011–2023
Smart Pointers & Modern C++
C++11 introduced std::unique_ptr and std::shared_ptr, wrapping raw pointers with RAII semantics. Understanding raw pointers remains essential, as smart pointers are built on top of them.

Despite decades of abstraction, the fundamental question remains the same: how does a program refer to a specific piece of data in memory without embedding the data itself? Pointers answer this question by storing the address at which data resides, decoupling the reference from the value. This indirection is what enables dynamic data structures, polymorphism, hardware interaction, and efficient parameter passing—all cornerstones of C++ systems programming.

Core Principles & Definitions

Before writing any pointer code, it is important to internalize a small set of foundational ideas. These principles are not merely syntactic conventions—they reflect the physical reality of how data is organized in a computer's memory subsystem. A solid conceptual understanding here prevents the class of subtle bugs (dangling pointers, memory leaks, segmentation faults) that plague even experienced developers.

1

Memory as a Numbered Array of Bytes

A computer's memory (RAM) can be modeled as a vast, contiguous array of bytes, each identified by a unique non-negative integer called its address. On a 64-bit system, addresses range from 0x0 to 0xFFFFFFFFFFFFFFFF.
2

Variables Occupy Addressed Locations

When you declare int x = 42;, the compiler reserves a block of contiguous bytes (typically 4 for an int) and associates the name x with the starting address of that block.
3

The Address-Of Operator (&)

The unary & operator applied to a variable yields a value of type "pointer to T." For example, &x produces the memory address where x is stored.
4

Pointers Are Typed Variables

A pointer is a variable whose value is an address. Declaring int* p = &x; creates a variable p that stores the address of an int. The type tells the compiler how many bytes to read when dereferencing.
5

The Dereference Operator (*)

The unary * operator (when applied to a pointer) follows the stored address and accesses the value at that location. *p reads or writes the value of x through p.
✦ KEY TAKEAWAY
Think of memory as a row of numbered post-office boxes. Each box has a unique number (the address) and contains some contents (the value). A pointer is simply a slip of paper inside one box that reads 'go look in box #N.' The address-of operator writes that slip; the dereference operator follows the instruction and opens box #N.

Visual Explanation — Memory Layout

The following diagram illustrates the relationship between a variable, its address, and a pointer variable that stores that address. Each rectangle represents a block of memory; the label above indicates the variable name, and the label below shows the memory address. The arrow from p to x visualizes the indirection—p does not contain the value 42 itself; it contains 0x7FFC, the address where 42 resides.

The violet-bordered cells represent the variable x (value 42) at address 0x7FFC. The cyan-bordered cell represents pointer p at address 0x8010, whose stored value is 0x7FFC—the address of x. The dashed arrow traces the indirection that occurs when you write *p.

Notice several key details in the diagram. First, p itself occupies memory—it is not a magical reference but a concrete variable with its own address (0x8010) and its own size (typically 8 bytes on a 64-bit machine). Second, the value stored inside p is not 42; it is 0x7FFC. This distinction between the value of the pointer (an address) and the value at the pointed-to location (the actual datum) is the single most important conceptual hurdle when learning pointers. Third, the dashed arrow is a visualization of what *p does at runtime: the CPU uses the address stored in p to fetch or modify the contents of that memory location.

How Addresses and Pointers Work Under the Hood

Although pointer syntax in C++ is simple, the underlying mechanism involves the cooperation of the compiler, the operating system's virtual memory subsystem, and the CPU's addressing modes. Understanding this pipeline removes the mystery from pointer behavior and clarifies why certain operations (like pointer arithmetic) work the way they do.

Address Spaces and Virtual Memory

Modern operating systems provide each process with its own virtual address space, an illusion of a large, contiguous block of memory. On a 64-bit system, this address space spans 2⁶⁴ bytes in theory, though only a subset is actually mapped to physical RAM at any time. When your C++ program takes the address of a variable using &x, the value you get is a virtual address. The CPU's Memory Management Unit (MMU) translates this virtual address to a physical address transparently; the programmer never sees the physical address.

POINTER SIZE
sizeof(T*) = sizeof(void*) = W / 8 bytes
Where W is the native word size in bits (32 or 64). On a 64-bit system, every pointer occupies 8 bytes regardless of the type it points to.

Pointer Arithmetic and Type Width

The type associated with a pointer determines how pointer arithmetic is scaled. When you write p + 1 where p is an int*, the compiler does not add 1 byte to the address; it adds sizeof(int) bytes (typically 4). This allows pointer arithmetic to naturally traverse arrays element by element.

POINTER ARITHMETIC FORMULA
address(p + n) = address(p) + n × sizeof(T)
Where p is a pointer of type T*, n is an integer offset, and sizeof(T) is the size of type T in bytes. This is why the type system matters for pointers—it governs the step size.

The Null Pointer

A pointer that does not currently point to any valid object should be assigned the value nullptr (introduced in C++11). Conceptually, nullptr represents a distinguished address guaranteed to never correspond to a valid object. Dereferencing a null pointer is undefined behavior and typically results in a segmentation fault. Always check pointers before dereferencing in production code, or prefer references and smart pointers that eliminate the possibility of null.

📌 Legacy Note
In pre-C++11 code, you will encounter NULL (a macro defined as 0 or (void*)0) used in place of nullptr. Prefer nullptr in modern C++ because it has its own distinct type (std::nullptr_t), which avoids ambiguity in overloaded function resolution.

Detailed Breakdown — Types of Pointers

C++ provides several flavors of pointer-like constructs, each suited to different use cases. Understanding the taxonomy helps you choose the right tool and reason about ownership and lifetime semantics. The diagram below categorizes the major pointer-related constructs available in modern C++ and shows how they relate to one another.

Taxonomy of pointer-related constructs in C++. Raw pointers store addresses directly, while references provide syntactic sugar for indirection without explicit address manipulation. Smart pointers wrap raw pointers with automatic lifetime management.
Comparison of pointer-like constructs in C++
ConstructCan Be Null?Can Rebind?Syntax for Access
T* (raw pointer)Yes — use nullptrYes — assign a new address*p or p->
T& (lvalue reference)No — must bind at initializationNo — bound for lifetimeUse the name directly (e.g., r)
unique_ptr<T>Yes — default-constructed is nullYes — via std::move*up or up->
shared_ptr<T>YesYes — reference-counted*sp or sp->

Worked Example — Tracing Pointer Operations

Consider the following short C++ program. We will trace the state of memory after each statement to build intuition for how addresses and pointer values change.

💻 Code Listing
int a = 10; int b = 20; int* p = &a; // p points to a *p = 15; // modify a through p p = &b; // redirect p to b int c = *p + a; // c = b + a
Step-by-Step Memory Trace
1
Step 1 — Declare and Initialize a and bThe compiler allocates space for two int variables. Suppose a is placed at address 0x1000 and b at address 0x1004. After these declarations, address 0x1000 contains the bit pattern for 10, and address 0x1004 contains 20.
a = 10 @ 0x1000, b = 20 @ 0x1004
2
Step 2 — Declare Pointer p = &aThe pointer variable p is allocated at, say, 0x1010. The address-of operator &a evaluates to 0x1000, so p stores the value 0x1000. Now *p is an alias for a.
p = 0x1000 @ 0x1010 → *p = 10
3
Step 3 — Dereference and Assign: *p = 15The CPU reads the value stored in p (0x1000), then writes 15 to that address. This modifies a in place. The pointer p itself is unchanged.
a = 15, p still = 0x1000
4
Step 4 — Redirect Pointer: p = &bWe assign the address of b (0x1004) to p. Now p no longer points to a; it points to b. This does not affect the value of a or b at all—only the address stored in p changes.
p = 0x1004 → *p = 20
5
Step 5 — Compute c = *p + aThe expression *p dereferences p which now points to b, yielding 20. Adding a (which was modified to 15 in Step 3) gives 35.
c = 20 + 15 = 35

Strengths & Pitfalls of Raw Pointers

Raw pointers are one of the most powerful features in C++, but their power comes with responsibility. The table below summarizes the primary strengths and the most common pitfalls. Awareness of these trade-offs is the first step toward writing pointer-safe code, and understanding them motivates the existence of references and smart pointers in modern C++.

Strengths vs. pitfalls of raw pointers in C++
StrengthsPitfalls
Enable dynamic memory allocation—create objects whose lifetimes are not bound to any scopeMemory leaks—forgetting to delete heap-allocated memory
Allow efficient pass-by-address, avoiding copies of large objectsDangling pointers—using a pointer after the object it refers to has been deallocated
Support polymorphism through base-class pointers and virtual dispatchNull dereference—dereferencing a pointer that is nullptr
Enable pointer arithmetic for low-level memory traversal and hardware interactionBuffer overflows—arithmetic that moves the pointer past allocated bounds
Provide the foundation for data structures like linked lists, trees, and graphsType confusion—improper casts (e.g., reinterpret_cast) leading to undefined behavior
✦ KEY TAKEAWAY
Think of a raw pointer as a master key that opens any door in a building. It provides maximum flexibility—you can enter any room—but if you use it carelessly, you may walk into a room that has been demolished (dangling pointer), locked permanently (null dereference), or belongs to someone else (buffer overflow). Modern C++ offers smart pointers as controlled keycards: they automatically deactivate when the room is destroyed, enforcing safe lifetime management at the type-system level.

Connection to Advanced Topics

Mastering the conceptual model of addresses and pointers opens the door to several advanced C++ topics. The raw pointer model you have learned in this lesson is the substrate upon which references, smart pointers, polymorphism, and even the Standard Template Library (STL) iterators are built. The table below maps core pointer concepts to the advanced features they enable.

How core pointer concepts connect to advanced C++ features
Core Pointer ConceptAdvanced TopicConnection
Address-of / dereferenceReferences (T&)A reference is implemented as a pointer internally but hides the address-of and dereference syntax, guaranteeing non-null binding.
Pointer arithmeticSTL IteratorsRandom-access iterators generalize pointer arithmetic to arbitrary containers, using operator overloading to mimic p + n semantics.
Heap allocation via newRAII & Smart Pointersunique_ptr and shared_ptr wrap the raw pointer returned by new and call delete automatically when the object goes out of scope.
Base-class pointersRuntime PolymorphismStoring a derived-class address in a base-class pointer enables virtual dispatch: the vtable pointer (hidden inside the object) resolves the correct function at runtime.
Function pointersCallbacks & std::functionC-style callbacks use function pointers directly; std::function abstracts over them to support lambdas and functors.

As you continue through this course, you will see that nearly every abstraction in C++ can be traced back to the idea of indirection through an address. Virtual dispatch works because objects contain hidden pointers to vtables. Smart pointers work because they encapsulate a raw pointer with deterministic destruction. Iterators work because they abstract over pointer arithmetic. Building a rock-solid mental model of addresses and pointers now will pay dividends in every subsequent topic.

Practice Problems

PROBLEM 1 — CONCEPTUAL
Explain the difference between the value of a pointer and the value at the address a pointer holds. Why is it essential to distinguish these two concepts when reasoning about pointer code?
PROBLEM 2 — BASIC CALCULATION
Given double arr[5]; where arr decays to a pointer with value 0x2000 and sizeof(double) = 8, what is the numeric address produced by the expression arr + 3? Show your calculation.
PROBLEM 3 — INTERMEDIATE
Trace the following code and determine the final values of x, y, and *q: int x = 5, y = 10; int* q = &x; *q = *q + y; q = &y; *q = *q - 3;
PROBLEM 4 — APPLIED
Write a C++ function void swap(int* a, int* b) that swaps the values of two integers using pointer dereferencing. Then explain why passing int by value (instead of by pointer) would fail to achieve the swap in the caller's scope.
PROBLEM 5 — CRITICAL THINKING
Consider the following code: int* createLocal() { int val = 42; return &val; } Explain why the pointer returned by this function is dangerous. What category of pointer bug does this represent, and how would you fix it using (a) dynamic allocation and (b) a smart pointer?

Summary — Addresses & Pointers

Every variable in a C++ program occupies a location in memory identified by a unique address. The address-of operator (&) retrieves this address, and a pointer is a typed variable that stores such an address. The dereference operator (*) follows the stored address to access the pointed-to value—a process called indirection. Pointer arithmetic scales offsets by sizeof(T), enabling natural traversal of contiguous data like arrays.

While raw pointers provide maximum flexibility for dynamic allocation, polymorphism, and hardware interaction, they introduce risks including dangling pointers, memory leaks, and null dereferences. Modern C++ mitigates these risks through references (which cannot be null or rebound) and smart pointers (unique_ptr, shared_ptr) that wrap raw pointers with RAII-based automatic lifetime management. A thorough conceptual understanding of raw addresses and pointers is the prerequisite for mastering all of these higher-level abstractions.

Varsity Tutors • C++ • Addresses & Pointers — Understand addresses and pointers conceptually