TRT
Sign in

Foundations · Module 3 · Lesson 6 of 10

Pointers: Variables That Hold Addresses

Treat an address as a value: take it with the ampersand, store it in a pointer, follow it with the star, and see what that costs, why it makes data structures possible, and how uninitialized or null pointers crash programs.

FreeAbout 8 minutes

By the end you can

  • Read and write through a pointer using the address-of and dereference operators and predict the results
  • Explain what a pointer variable contains and how big it is
  • Explain how copying a pointer differs from copying a value
  • Identify uninitialized and null pointers as the source of crashes

Builds on Variables Are Names for Bytes, Passing by Value, by Reference and by Const Reference.

01 · A question first

In lesson 3 you printed the address of a variable and saw a number such as 0x7ffc2b3a9e5c. What if a variable could hold that number? What could you do with it, what does it cost, and what happens when the number is wrong?

Make a guess, then step through the trace below to check it.

02 · Watch it run

Taking an address and following itstep 1 of 4
int x = 7;int* p = &x;*p = 42;int y = *p;
  1. var 0x @1000: 7(looked at in this step)
  2. var 1p @1008: --
  3. var 2y @1016: --

x is a variable of 4 bytes at some address, which we call 1000 for readability. Real addresses are large numbers such as 0x7ffc2b3a9e5c. The three cells are the variables x, p and y, each shown with its address and its contents. x holds 7.

Machine work: store 7 at address 1000

03 · The idea

Start with the problem

A variable names a few bytes, and a reference is another name for the same bytes. Sometimes a program needs something more flexible: a value that says where an object is. You may want to choose at run time which of several objects to work on, store the location of an object inside another object, refer to memory that came from the heap, or call an interface written in C.

You have already seen what such a value looks like. In lesson 3 the expression &a produced an address. A pointer is a variable that stores one.

How the idea works

The two operators. The address-of operator & gives the address of a variable. The dereference operator * follows an address to the object stored there. The type int* means "a pointer to an int":

cpp
int x = 7;
int* p = &x;      // p holds the address of x
*p = 42;          // follow the address and write: x is now 42
int y = *p;       // follow the address and read: y is 42

The trace walks through exactly these lines. Notice the two meanings of the star, as with the ampersand in lesson 7 of module 2: in a declaration such as int* p it forms a pointer type, and in an expression such as *p it follows the pointer.

A pointer is a variable. It has its own address and its own bytes, 8 of them on a 64-bit machine. Step 2 shows its contents as a plain number. A pointer to a char and a pointer to a huge object are the same size, because both are only addresses.

The type says how to read. Lesson 4 taught that bytes mean nothing without a type. An int* tells the compiler to read four bytes as an int, and a double* says to read eight bytes as a double. A void* holds an address with no type, and cannot be followed until it is converted. C interfaces use it for "any object".

Copying a pointer copies the address. After int* q = p; both pointers lead to the same x. A write through either one changes x, which is called aliasing. This is a very different behavior from the value semantics of lesson 3, where copying a variable produced an independent copy.

Pointers and references. A reference is also an address underneath, as lesson 7 of module 2 said, but a pointer is a full variable. It can be reassigned to point elsewhere, it can be null, it can be stored in arrays and objects, and you must dereference it yourself. Lesson 8 compares the two.

const and pointers. Read a declaration from right to left. const int* p is a pointer to a constant int: you cannot write through it, but you can repoint it. int* const p is a constant pointer to an int: you can write through it, but you cannot repoint it.

Printing. std::cout << p prints the address, and std::cout << *p prints the value it leads to. A const char* is special. The stream has a separate overload, as lesson 8 of module 2 explained, that treats it as a C-string, so std::cout << greeting prints the text instead of an address. That also solves the mystery of lesson 5: const char* points at the first character of a string.

Why it is correct

A pointer is the machine's own way of addressing memory made visible. Every byte has an address, as lesson 3 explained, and the CPU's load and store instructions take an address from a register. Following a pointer is therefore one instruction, with no lookup and no bookkeeping.

The type system keeps the use of the address consistent. A double* cannot be assigned to an int* without an explicit cast, so the bytes are read with the type they were written with. And indirection is what makes sharing possible: two names for one object, or a chain of objects each pointing to the next, which is how linked lists, trees and graphs are built.

What it costs

Space. A pointer takes 8 bytes, more than the int it often points to. A linked list of integers pays 8 bytes of pointer per element, plus the overhead of one allocation each, which adds up to several times the size of the data.

Cache misses. Objects joined by pointers can sit anywhere in memory. Following a pointer to a distant object may take around 100 nanoseconds if it must come from main memory, instead of about 1 nanosecond from the cache. This pointer chasing is the main reason a std::vector usually beats a linked list in practice.

Indirection. Reading through a pointer costs one extra load compared with using a variable directly, which is cheap when the pointer is in the cache.

Lost optimizations. The compiler cannot assume that two pointers lead to different objects, so it must be careful about reordering reads and writes through them.

Pitfalls and variations

The wild pointer. An uninitialized pointer holds a garbage address, and following it is undefined behavior. Always initialize it: int* p = nullptr; if there is nothing to point to yet.

The null pointer. Following a null pointer is also undefined behavior, but in practice it usually stops the program with a segmentation fault, since the lowest addresses are unmapped. Lesson 8 shows how to test for it.

The dangling pointer. A pointer to an object that no longer exists, such as a local variable of a function that returned, as in lesson 6 of module 2, leads to freed or reused memory. Lesson 10 returns to this.

The declaration trap. In int* a, b; only a is a pointer, and b is an int, because the star belongs to the name. Declare one variable per line.

Mixing up p and *p. p = 5 tries to store a number into a pointer, which the compiler rejects. *p = 5 writes 5 into the object it points to.

Variations. A pointer can point to another pointer, as in int**. There are pointers to functions, which let you choose which code to call. Smart pointers, in the Core level, wrap a raw pointer in an object that manages its lifetime. And the second parameter of main, the char* argv[] that lesson 2 of module 1 mentioned, is an array of pointers to C-strings, which hold the command-line arguments.

Where it is used

Pointers underlie C interfaces, linked lists, trees and graphs, and the heap, which lesson 9 introduces. Polymorphism, in the Core level, works through pointers or references to objects. The iterators of the standard library behave like pointers, and learning to read pointer code is a requirement for reading almost any systems code.

The next lesson, "Pointer Arithmetic and the Link to Arrays", shows what happens when you add a number to a pointer, and why an array turns into a pointer when it is passed to a function.

04 · Prove it

Question 1 of 4 · answer from memory

After `int x = 7; int* p = &x; *p = 42;`, what is the value of `x`?

05 · Practice

Something wrong with this lesson? Report a problem

Not started

Sign in to save your learning progress.

Sign in to save