Pointers & References

Pointers and references let a program work with the location of data rather than a copy of the data itself. This is one of the most powerful — and, in unsafe languages, most dangerous — concepts in programming. A full, dedicated Pointers & References module with detailed memory diagrams is planned as a future lesson; this section covers the essential foundation.

A pointer is a variable whose value is a memory address — the location where some other piece of data actually lives, rather than the data itself. A reference (in languages that distinguish the two) is a similar concept — a way to refer to existing data rather than copying it — but is usually restricted to be safer and less directly manipulable than a raw pointer.

Sometimes you need to refer to the same piece of data from multiple places without copying it (efficient for large data), or you need a function to be able to modify the caller's original data directly rather than just a local copy. Pointers and references make both possible.

Passing a large data structure to a function without copying it, building linked data structures (linked lists, trees) where each piece explicitly points to the next, and letting one part of a program share and modify data owned by another part, are all classic pointer/reference use cases.

A pointer is like a piece of paper with someone's home address written on it, rather than the actual house. You can hand that piece of paper to someone else, and they can go to that address and see (or even rearrange furniture inside) the actual house — but the piece of paper itself is small and easy to pass around, compared to physically moving the whole house.

&x gets the memory address of x. int *p declares p as a pointer to an int, and p = &x stores that address in p. *p = 20 dereferences p (follows the address) and writes 20 into the memory location p points to — which is the same memory x itself occupies, so x's value changes too.

Dereferencing a pointer means following it to access (read or write) the actual data at the address it stores; the & (address-of) and * (dereference) operators in C-family languages are opposite operations of each other.

A null pointer (or None/null/nil in higher-level languages) is a special value meaning 'this pointer doesn't currently point to any valid data' — dereferencing a null pointer is a common and serious bug (a null-pointer dereference), typically crashing the program or, in some languages, throwing a catchable exception instead.

References (in languages like C++, or conceptually in Java/Python/JavaScript's object variables) generally cannot be null in the same unrestricted way raw pointers can, and typically cannot be reassigned to refer to different data after creation (in C++'s reference specifically) — trading some of a raw pointer's flexibility for additional safety guarantees.

Pointer arithmetic (adding or subtracting an integer from a pointer, moving it to point at a nearby memory location, common in C) is powerful for working with arrays and buffers directly, but is also a major source of memory-safety bugs if the resulting pointer strays outside a valid, intended range.

Dereferencing a null or uninitialized pointer, one of the single most common crash causes in pointer-using languages like C and C++.

Using a pointer after the memory it points to has been freed (a dangling pointer / use-after-free) — the memory might still 'look' valid for a while, making this bug intermittent and hard to reproduce reliably.

Confusing a pointer's own address (where the pointer variable itself lives in memory) with the address it stores (what it points to) — a frequent source of confusion for learners first encountering pointers.

Performing pointer arithmetic that accidentally walks outside the bounds of the array or buffer it was meant to stay within, corrupting unrelated memory.

Passing a pointer (or reference) to a large structure into a function instead of passing the whole structure by value, avoiding an expensive copy of potentially large data.

Using a function that returns success/failure via its return value, while writing an actual result out through an output pointer parameter — a common C pattern for functions needing to report both a status and a computed value.

Preferring references or smart pointers (covered further in the future C++ Advanced Memory module) over raw pointers wherever a language and its idioms allow it, reducing the risk of the classic pointer bugs above.

Pointers, addresses, and virtual memory The address stored in a pointer is, on virtually all modern operating systems, a virtual address specific to the running process, not a literal physical RAM address — the CPU's memory management unit translates it to a physical address behind the scenes, and this indirection is exactly what lets each process believe it has its own private, contiguous address space regardless of what's physically happening in RAM. This is covered fully in the future Memory & Program Execution and Operating System Connection modules.

Void pointers and type erasure A void pointer (void* in C) can point to data of any type but carries no type information itself — the programmer is responsible for casting it back to the correct concrete type before dereferencing it, and doing so incorrectly is undefined behavior with no compiler protection whatsoever. This makes void* extremely flexible (used throughout C's standard library, for instance in generic sort/comparison functions) but also one of the least type-safe constructs in mainstream systems programming.

Smart pointers and ownership models Modern C++ (unique_ptr, shared_ptr, weak_ptr) and Rust's ownership system both aim to preserve pointer-level efficiency while eliminating entire classes of manual-memory-management bugs — automatically freeing memory when it's provably no longer needed (unique_ptr's exclusive ownership, Rust's move semantics) or safely sharing ownership with reference counting (shared_ptr, Rust's Rc/Arc) — a topic developed fully in the future C++ Advanced Memory and Modern Memory-Safety Models modules.

What's the difference between a pointer and a reference?

A pointer is an explicit variable holding a memory address; it can typically be reassigned, can be null, and often supports pointer arithmetic. A reference (in languages that distinguish the two, like C++) refers to existing data similarly, but generally cannot be null, cannot be reassigned once bound, and doesn't support arithmetic — trading flexibility for safety.

What is a dangling pointer?

A pointer that still holds the address of memory that has already been freed or has otherwise gone out of scope — the memory it points to is no longer valid, and using (dereferencing) that pointer produces undefined, unpredictable behavior.

What's the difference between the & and * operators in C?

& is the address-of operator — given a variable, it produces a pointer to that variable's memory location. * is the dereference operator — given a pointer, it accesses (reads or writes) the actual value stored at the address the pointer holds. They're inverse operations of each other.

Why is void* considered less safe than a typed pointer?

Because it carries no information about what type of data it actually points to — the compiler cannot check that a cast back to a concrete type is correct, so an incorrect cast produces undefined behavior with no compile-time or even necessarily immediate runtime warning.

int x = 10;
int *p = &x;      // p holds the address of x
*p = 20;            // dereference p, change the value at that address
printf("%d", x);   // 20 — x itself was changed through the pointer