Menu

C++ Pointers: Address-Of, Dereference, and nullptr

C++ pointers explained from scratch - declaring a pointer, the & address-of and * dereference operators, nullptr, pointers to arrays, and the dangling-pointer and uninitialized-pointer gotchas that cause crashes.

This page includes runnable editors - edit, run, and see output instantly.

A Variable That Holds an Address

Every variable lives somewhere in memory, at a numbered location called its address. Most of the time you don't care where - you just use the variable's name. A pointer flips that around: it's a variable whose value is an address. Instead of holding 42, it holds "the place where 42 is stored."

This indirection is what makes pointers powerful. Functions can change a caller's variable through one, data structures like linked lists chain nodes together with them, and (as you'll see in dynamic memory) they're how you reach memory you allocate at runtime.

The & in &score is the address-of operator - it produces the location of score. The * in *p is the dereference operator - it follows the address back to the value living there.

The Two Operators: & and *

The single most confusing thing for newcomers is that * means two different things depending on where it sits. Keep these straight:

int* p;     // DECLARATION: "p is a pointer to int"
p = &x;     // & = address-of: store the address of x in p
int y = *p; // * = dereference: read the value p points at
*p = 99;    // dereference on the left: write through the pointer

In a declaration, * is part of the type. In an expression, * does work. Once a pointer is set up, dereferencing it gives you full read/write access to the original variable:

Notice you never touched health by name after line 1, yet its value kept changing. That's the whole point: hp is an alias to the same storage. Spacing (int* p, int *p, int*p) is cosmetic and identical to the compiler - this guide uses int* p.

nullptr: Pointing at Nothing

A pointer that doesn't point anywhere should be set to nullptr (C++11). It's a clear, type-safe way to say "no target yet," and it gives you something to test against before you dereference.

Prefer nullptr over the legacy NULL macro or a bare 0. Because nullptr has a real pointer type, it never gets misread as the integer 0 during overload resolution - a subtle bug the old style could cause.

Gotcha - the null dereference. Reading or writing through a null (or uninitialized) pointer is undefined behavior, usually an instant crash:

int* p = nullptr;
cout << *p;   // CRASH - dereferencing null is undefined behavior

Always guard with if (p) (or if (p != nullptr)) before you dereference anything that might be null.

Pointers and Arrays

An array's name decays to a pointer to its first element, so pointers and arrays are deeply intertwined. Adding 1 to a pointer doesn't add one byte - it advances by one element, which is what makes pointer arithmetic work:

p[i] and *(p + i) are literally the same expression - that equivalence is why arrays are zero-indexed. The classic bug here is walking past the end: nums + 4 is a valid one-past-the-end marker to compare against, but dereferencing *(nums + 4) reads out of bounds. Off-by-one errors with pointers are a leading cause of crashes and silent corruption, so be deliberate about your stop condition.

const and Pointers

const can apply to what the pointer points at, to the pointer itself, or both. Read the declaration right-to-left to decode it:

const int* p;        // pointer to const int  - can't change *p, can repoint p
int* const p = &x;   // const pointer to int  - can change *p, can't repoint p
const int* const p = &x; // both locked

This matters constantly in real code. A function that promises not to modify your data takes a pointer-to-const:

Marking the pointee const documents intent and lets the compiler stop accidental writes - free safety with zero runtime cost.

The Big Trap: Dangling Pointers

A dangling pointer points at memory that no longer holds the value you expect - the variable went out of scope, or the memory was freed. Dereferencing it is undefined behavior, and the nasty part is that it often seems to work until it doesn't.

int* makeBad() {
    int local = 5;
    return &local;   // BUG: local dies when the function returns
}                    // the returned pointer now dangles

The address is still a valid number, but it points at a stack slot that's been reclaimed - reading it gives garbage or crashes. The same thing happens if you keep a pointer to a deleted heap object or to an element of a vector that later reallocates.

Three rules keep you safe:

  • Never return the address of a local variable. Return by value, or have the caller own the storage.
  • Set a pointer to nullptr after the thing it points to is gone, and check before use.
  • For ownership and lifetimes, reach for smart pointers instead of raw new/delete - they free memory automatically and shrink this entire class of bug.

Next: References vs Pointers

Pointers aren't the only way to refer to another variable indirectly. C++ also has references, which feel similar but can't be null, can't be reseated, and use cleaner syntax. Next we'll put them side by side in references vs pointers so you know exactly which tool to reach for - and why most modern C++ prefers references when it can use them.

Frequently Asked Questions

What is a pointer in C++?

A pointer is a variable that stores the memory address of another value rather than the value itself. You declare one with * (e.g. int* p), take an address with the & operator (p = &x), and read or write the value it points at by dereferencing with *p.

What is the difference between & and * in C++ pointers?

In a pointer context, & is the address-of operator - &x gives you the address of x. * does two jobs: in a declaration (int* p) it marks the variable as a pointer, and in an expression (*p) it dereferences the pointer to reach the value stored at that address.

What is nullptr in C++ and why use it instead of NULL?

nullptr is a type-safe null pointer literal added in C++11. It means "points at nothing." Prefer it over the old NULL or 0 because nullptr is a real pointer type, so it never gets mistaken for an integer in overload resolution. Always check if (p) before dereferencing - dereferencing a null pointer is undefined behavior.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED