Pointers and References

The Level 0 memory lesson showed that every value in a running program sits somewhere: a numbered byte address in the process's memory. Most languages in this Academy hide those numbers behind references you cannot inspect. C++ hands them to you directly. A pointer is a variable whose value is an address, and learning to read pointer code is learning to see memory the way the machine, the debugger, and the reverse engineer all see it.

Two operators, one mirror

Two operators do almost all of the work. The address-of operator & asks a variable where it lives and hands back the address. The dereference operator * goes the other way: given an address, it follows the pointer to the value stored there. They are inverses, and reading pointer code out loud with the words at and the value at makes most of it click.

// inside int main()
int score = 82;
int* p = &score;   // p holds the ADDRESS of score

std::cout << score << "\n";  // 82  (the value)
std::cout << p << "\n";      // something like 0x7ffee4c0a9c4 (the address)
std::cout << *p << "\n";     // 82  (follow the pointer: the value AT p)

*p = 90;                     // write THROUGH the pointer...
std::cout << score << "\n";  // 90  ...and score itself changed

That last pair of lines is the important one. Writing through *p changed score, because p does not hold a copy of the value, it holds the location of the original. Two names, one box. Every language you have met does this constantly under the hood, when a Lua table or a Java object is passed around, what travels is a reference to one shared thing, and C++ simply makes the mechanism visible and spellable.

A street address is the classic picture, and it earns its place. Writing your address on a card does not copy your house onto the card. Handing the card to a friend (copying the pointer) does not build a second house. And if two people hold cards with the same address, a change one of them makes to the house is seen by the other, because there is only one house. The card can also be blank, which is the next idea.

nullptr: the pointer that points at nothing

A pointer variable can hold a special value meaning deliberately pointing at nothing: nullptr. It is C++'s pointer-shaped nothing, the same role nil plays in Lua and null in JavaScript, but scoped strictly to pointers. Dereferencing a null pointer is undefined behavior, usually a crash, so pointer code checks before it follows. The Core Guidelines sharpen this into a design signal: prefer a pointer parameter precisely when no argument is a valid option, and a reference when the argument must exist. The type then documents the contract.

// inside int main()
int* target = nullptr;         // currently aimed at nothing
if (target != nullptr) {
  std::cout << *target << "\n";  // safe: only follow a non-null pointer
} else {
  std::cout << "no target\n";    // this branch runs
}

Pointer arithmetic: why arrays index in O(1)

The arrays lesson claimed that scores[i] costs one multiply and one add, address = base + index times element size. Pointers are where that claim becomes literal. An array's name in C++ is convertible to a pointer to its first element, and adding an integer to a pointer moves it forward by whole elements, the compiler scales by the element size for you. The expression scores[2] is defined as *(scores + 2). Indexing is not a search, it is arithmetic on an address, and you can watch the arithmetic below.

This is also exactly how a linked list differs, one lesson back: an array's next element is at a computable address, a list node's next element is wherever its stored pointer says. And it is why walking off the end of an array is so dangerous in C++: scores + 9 is a perfectly formed address computation whether or not there is a tenth element, and dereferencing an invalid pointer is undefined behavior. Higher-level languages bounds-check every index; C++ trusts you, which is precisely what buffer-overflow attacks have exploited for decades.

References: the alias that cannot be null

C++ has a second, tamer tool for two names, one box: the reference. A reference is an alias, another name permanently bound to an existing variable. It must be initialized to something, can never be null, and can never be re-aimed at a different variable, three guarantees a raw pointer does not make. Function parameters are where references shine: passing by reference to const lets a function read a large value, like a std::string, without copying it, which is the Core Guidelines' default advice for expensive-to-copy in parameters. You saw the syntax in the Level 1 functions pane: const std::string& name.

// inside int main()
int health = 100;
int& hp = health;   // hp is another NAME for health, not a copy
hp -= 35;
std::cout << health << "\n";  // 65: same box, two labels

// contrast: a plain copy
int snapshot = health;
snapshot -= 65;
std::cout << health << "\n";  // still 65: copies are independent

The modern discipline: raw pointers do not own

Historically, C++ pointers did double duty: they located values AND owned heap memory that somebody had to remember to free. Modern C++ splits those jobs. The Core Guidelines are blunt about it: a raw pointer is non-owning, and ownership is never transferred by handing someone a T*. Raw pointers and references locate and observe; owning is the job of the RAII types two lessons from now. Read new C++ with that split in mind and the pointer soup untangles: a T* in a well-kept codebase is just an address that someone else keeps alive.

Pointers and protected code

Pointers are the native vocabulary of the attacking side. A debugger watch, a memory dump, a cheat tool scanning a game for its health value: all of them are walking addresses and dereferencing, exactly the operations this lesson named. When Level 5 shows run-and-dump recovering decoded strings from a live process, the dump is a wall of addresses and the analyst's job is deciding which are pointers to follow. Understanding the pointer picture from the defender's chair is understanding what a memory snapshot does and does not give away.

A pointer stores an address; & takes an address and * follows one. nullptr is the deliberate nothing, checked before every dereference. Pointer plus integer is scaled address arithmetic, which is why arrays index in O(1) and why walking past the end is undefined. References are non-null aliases for values that must exist, and in modern C++ a raw pointer locates but never owns.

Frequently asked questions

Do Lua, Python, and JavaScript have pointers?

Not spellable ones, but the mechanism is everywhere: variables holding tables, lists, and objects hold references, which are addresses managed by the runtime. That is why passing an object to a function and mutating it there changes the caller's object. C++ differs in exposing the address as a value you can print, compare, and do arithmetic on, powers the managed languages deliberately withhold.

What actually happens when a null or dangling pointer is dereferenced?

The language standard says undefined behavior, meaning anything is permitted. In practice a null dereference usually hits an unmapped page and the operating system kills the process, the familiar segmentation fault, while a dangling pointer, one aimed at memory that has been freed, is worse: it often reads or corrupts whatever now lives there and fails far from the real bug. The stack and heap lesson next shows how lifetimes create dangling pointers.

Keep learning

  • Where Values Live: Memory, Stack, and Heap
  • Arrays and Dynamic Arrays
  • Linked Lists
  • Stack vs Heap Allocation
  • Lua Obfuscator
  • JavaScript Obfuscator
  • Python Obfuscator

All lessons