Pointers

CS310 — Understanding Memory Addresses in C++

A pointer is a variable that holds a memory address. This deck covers declaration, dereferencing, pointer arithmetic, arrays, and function parameters.

What is a Pointer?

Every variable lives at a specific memory address

int count = 5;
short status = 2;
char letter = 'A';
// A pointer holds an ADDRESS
int *ptr = &count;
// ptr contains 0x0060 (the address of count)
// *ptr gives us 5 (the value at that address)
Analogy: Think of memory as a row of mailboxes. Each mailbox has an address (like 0x0060) and holds a value (like 5). A pointer is a slip of paper that has a mailbox address written on it.
Key insight: A pointer is just a variable — but instead of storing data like 5 or 'A', it stores the address of another variable.

Declaring & Initializing Pointers

Syntax, spacing, and the declaration trap

Declaration Syntax

dataType* pointerName;
// All equivalent:
int *intptr;
int* intptr;
int * intptr;
// Read as: "intptr can hold the
// address of an int"
Declaration Trap!
int* pI, pJ; // pJ is NOT a pointer!
// Equivalent to: int *pI, pJ;
int *pI, *pJ; // Both are pointers ✓

The * binds to the variable name, not the type. Each pointer variable needs its own *.

Initialization

// Initialize to an existing variable's address
int count = 5;
int *ptr = &count;
// Initialize to NULL (points to nothing)
int *ptr2 = NULL; // C style
int *ptr3 = nullptr; // C++11 style
Never dereference NULL!
int *ptr = nullptr;
cout << *ptr; // CRASH! Segfault

Always check for NULL before dereferencing: if (ptr != nullptr)

Rule of thumb: Always initialize pointers when you declare them — either to a valid address or to nullptr.

The Two Operators: & and *

Address-of and dereference — the yin and yang of pointers

Inverse operations: & gets the address of a variable. * follows an address to get the value. They undo each other: *(&x) == x.

Example: Pointers in Action

Step through a complete program and watch memory change

Click Step to trace the program...

Pointer Types & Safety

Type matching and the null pointer

Type Must Match

int area = 1;
double* pArea = &area; // ERROR!
// int* and double* are different types
int* pArea = &area; // OK ✓
Why types matter: The pointer type tells the compiler how many bytes to read when dereferencing. An int* reads 4 bytes, a double* reads 8 bytes. Mixing them would read the wrong amount of memory.

NULL / nullptr

// A pointer that points to nothing
int *ptr = nullptr;
// Always check before dereferencing!
if (ptr != nullptr) {
cout << *ptr; // safe
}
Uninitialized pointers are dangerous!
int *ptr; // garbage address!
cout << *ptr; // undefined behavior

An uninitialized pointer contains whatever was previously in that memory — it could point anywhere, causing hard-to-debug crashes.

Analogy: An uninitialized pointer is like having a random house address on a slip of paper — you might end up at a stranger's house, an empty lot, or a restricted zone. nullptr is like writing "NO ADDRESS" — at least you know not to follow it.

Pointer Assignment: pX = pY vs *pX = *pY

Two very different operations that look similar

Click a scenario to see the difference

Critical distinction:
  • pX = pY — copies the address (now both point to y)
  • *pX = *pY — copies the value (x gets y's value, pointers unchanged)

Arrays and Pointers

An array name IS a pointer to the first element

Equivalence: vals[i] is syntactic sugar for *(vals + i). The compiler translates array indexing into pointer arithmetic.
Precedence trap: *vals + 1*(vals + 1). The first dereferences then adds 1 to the value. The second adds 1 to the pointer then dereferences.

Pointer Arithmetic

Increment, decrement, add, subtract — all scaled by type size

int vals[] = {4, 7, 11};
int *valptr = vals;
valptr++; // now points at 7
valptr--; // back to 4
valptr += 2; // jumps to 11
// Pointer subtraction:
cout << valptr - vals; // # of ints apart
Click an operation to see pointer movement...
Scaled arithmetic: ptr + 1 advances by sizeof(int) = 4 bytes, not 1 byte. The compiler handles the scaling.

Array Access — Four Equivalent Ways

Array notation and pointer notation are interchangeable

#include <iostream>
using namespace std;
int main() {
int list[6] = {11,12,13,14,15,16};
for (int i = 0; i < 6; i++) {
cout << (list + i) // address
<< *(list + i) // pointer deref
<< list[i]; // array index
}
}
All four notations work:
  • list[i] — array subscript
  • *(list + i) — pointer arithmetic
  • ptr[i] — pointer with subscript
  • *(ptr + i) — pointer math on variable
No bounds checking! C++ won't stop you from accessing list[100] — it will happily read garbage memory. This is a common source of bugs and security vulnerabilities.

Comparing Pointers

Address comparison vs content comparison

int a = 5, b = 5, c = 10;
int *p1 = &a, *p2 = &b, *p3 = &a;
// Compare ADDRESSES
p1 == p2 // false (different addresses)
p1 == p3 // true (same address)
// Compare CONTENTS
*p1 == *p2 // true (both are 5)
*p1 == *c // false (5 ≠ 10)
Two kinds of equality:
  • p1 == p2 — "Do they point to the same memory location?"
  • *p1 == *p2 — "Do the values they point to happen to be equal?"
Analogy: Two people can have the same phone number written down (same address). Or they might have different phone numbers that happen to ring the same pizza place (different addresses, same value).

Challenge A — Predict the Memory State

Trace the pointer operations and predict the values

int a = 10, b = 20;
int *p = &a;
int *q = &b;
*p = *q + 5;
q = p;
*q = *q * 2;

Step-by-Step Trace

Try to work it out first, then click "Show Trace"...

Pointers as Function Parameters

Passing addresses to functions to modify the caller's variables

void doubleIt(int x, int *p) {
*p = 2 * x;
}
int main() {
int a = 16;
int b = 9;
doubleIt(b, &a);
cout << a; // prints 18
}
Three requirements for pointer parameters:
  1. * in the function parameter: int *p
  2. * in the function body to dereference: *p = ...
  3. & in the call to pass the address: doubleIt(b, &a)

Swap: Value vs Reference vs Pointer

Three approaches — only two actually work

By Value (FAILS)

void swap(int x, int y){
int temp = x;
x = y;
y = temp;
}
swap(a, b); // a,b unchanged!

Swaps local copies only. Originals untouched.

By Reference ✓

void swap(int &x, int &y){
int temp = x;
x = y;
y = temp;
}
swap(a, b); // a,b swapped!

x and y are aliases for a and b.

By Pointer ✓

void swap(int *x, int *y){
int temp = *x;
*x = *y;
*y = temp;
}
swap(&a, &b); // a,b swapped!

Dereference to access originals.

Reference vs Pointer: References are simpler (no & at call site, no * to dereference). Pointers are more flexible (can be reassigned, can be NULL, needed for dynamic memory).
Common mistake: Swapping the pointers themselves (x = y; y = x;) instead of the values they point to (*x = *y; *y = *x;). The first just changes local pointer copies!

Challenge B — Fix the Bug

This swap function doesn't work. Why?

Buggy Code

void swap(int *x, int *y) {
int *temp = x;
x = y;
y = temp;
}
int a = 5, b = 10;
swap(&a, &b);
cout << a << " " << b; // prints: 5 10

The Fix

Select the bug and click Check to see the fix...

Passing Arrays to Functions

Array parameters are always pointers under the hood

// These two signatures are identical:
void printArr(int list[], int size);
void printArr(int *list, int size);
// const prevents modification:
void printArr(const int *list, int size) {
for (int i = 0; i < size; i++)
cout << list[i] << " ";
}
int arr[] = {1, 2, 3, 4, 5};
printArr(arr, 5); // arr decays to pointer
Array decay: When you pass an array to a function, it "decays" to a pointer to its first element. The function receives just an address — it doesn't know the array's size. That's why you always pass size separately.

Reverse Array Example

void reverse(const int list[],
int newList[], int size) {
for (int i=0, j=size-1; i<size; i++, j--)
newList[j] = list[i];
}
int list[] = {1, 2, 3, 4, 5, 6};
int newList[6];
reverse(list, newList, 6);
// newList = {6, 5, 4, 3, 2, 1}
Why pass by pointer? Copying an entire array (potentially millions of elements) every time you call a function would be extremely wasteful. Passing just the address (8 bytes) is O(1) regardless of array size.
Use const when the function shouldn't modify the array. This catches accidental writes at compile time.

Swap by Pointer — Step Through

Watch the pointer-based swap execute step by step

Click Step to trace swap(&num1, &num2)...

Pointer Patterns Cheat Sheet

Quick reference for all pointer operations

Common Pointer Mistakes

Avoid these — they cause crashes, corruption, and security vulnerabilities

1. Uninitialized pointer
int *p; // garbage address
*p = 10; // writes to random memory!
int *p = nullptr; // ✓ safe
2. Dereferencing NULL
int *p = nullptr;
cout << *p; // SEGFAULT!
if (p) cout << *p; // ✓ check first
3. Pointer type mismatch
int x = 5;
double *p = &x; // type mismatch!
int *p = &x; // ✓ types match
4. Confusing pointer assignment vs value assignment
int a=5, b=10; int *p=&a, *q=&b;
p = q; // changes what p points to
*p = *q; // changes the value at p
5. Declaring multiple pointers
int* p, q; // q is int, NOT int*!
int *p, *q; // ✓ both are pointers
6. Operator precedence
int arr[] = {4, 7, 11};
*arr + 1 // = 5 (deref then add)
*(arr + 1) // = 7 (add then deref) ✓

Pointers — Summary

The mental model to carry forward

int *p;
declare pointer
p = &x;
assign address
*p
dereference
&x
address-of
p++
next element
*(p+i)
offset access
arr[i]≡*(arr+i)
array = pointer
void f(int *p)
pointer param
nullptr
null pointer
const int *p
read-only ptr
The one rule: A pointer stores an address. & gets addresses, * follows them. Everything else — arrays, arithmetic, parameters — builds on this.

Challenge C — Which Pointer Operation?

Pick the right approach for each scenario

1. Access the 3rd element of array arr

2. Make a function modify a caller's variable

3. Check if two pointers point to the same location

Quiz — Multiple Choice

Q1: What does &x return?

Q2: int *p, q; declares:

Q3: ptr+1 for an int* advances by:

Quiz — Trace the Pointers

What are the final values?

int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
int *q = arr + 3;
p++;
*p = *q;
q--;
*q = *p + 5;

Step-by-Step

Enter your answer first, then click Show Trace...

Quiz — Predict the Output

What does this program print?

void mystery(int *a, int *b) {
*a = *a + *b;
*b = *a - *b;
*a = *a - *b;
}
int main() {
int x = 7, y = 3;
mystery(&x, &y);
cout << x << " " << y;
}

Trace

Enter your answer, then click Check to see the trace.
Hint: This is a well-known trick. Think about what happens to *a and *b at each step.