Every variable is a box with an address. What if one box held the address of another? Watch what goes into p.
A pointer holds an address
Three boxes in main; p holds an address, drawn as an arrow
#include <stdio.h> int main(void){ int x = 5; int *p = &x; *p = 9; int y = 2; p = &y; *p = *p + 1; printf("x = %d, y = %d\n", x, y); printf("p = %p\n", (void *)p); return 0;}Output
main has started. Its frame already holds x, p and y; none has a value yet.
Describe this picture
The 14 lines of pointers.c with the line about to run marked, beside a picture of memory: the frame of main on the stack, drawn as three rows of 8 byte cells, higher addresses on top. x, an int of 4 bytes at 0x7fec, fills the right half of the top row; p, an int * of 8 bytes at 0x7fe0, fills the middle row; y, an int at 0x7fdc, fills the right half of the bottom row. The other byte cells are empty. Each box shows its type and name at the top left, its address at the top right, and its value in the middle, ”?” until a line writes one. Under the picture the program’s output appears once it prints, and under everything one sentence on the current step. The clip runs one line every 1.9 seconds, 17 seconds in all, and rests on the end. When int *p = &x; runs, x’s address flies from x’s corner into p and grows into an arrow from p to x. When *p = 9; runs, a dot runs along the arrow and 5 becomes 9 in x. When p = &y; runs, the arrow swings to y. The box the last line wrote is outlined. The transport below plays and pauses, steps one line back or forward, and scrubs; a paused step is kept in the page address as mem.step.
What is in p
From Variables live in memory (1.2): a variable is a box of a few bytes, and its address is the number of its first byte. An int takes 4 bytes here. The picture draws memory in rows of 8 bytes, higher addresses on top: x is at the top, p in the middle and y at the bottom.
Line 6, int *p = &x;, puts a number into p: 0x7fec. It is the number in x’s corner, x’s address. That is all a pointer is: a box that holds the address of another box. The arrow is a way of drawing that number. The box p holds 0x7fec and nothing else.
Line 7, *p = 9;, writes 9, yet the name x does not appear in it. The write goes to the address held in p, and the box at 0x7fec is x. Watch p’s box while it happens: it still holds 0x7fec. A write through a pointer changes the box it points to, not the pointer.
Line 9, p = &y;, is an ordinary assignment to p: p gets a new number, 0x7fdc, and the arrow swings to y. Line 10 reads 2 through the arrow, adds 1 and writes 3 back into y. Line 12 prints p itself, and out comes y’s address.
Now a prediction. Step back until the mark sits on line 9. If that line were deleted, which box would line 10 change, and what would line 11 print?
Without line 9, p still holds 0x7fec when line 10 runs. So line 10 reads 9 out of x and writes 10 back into x, and y keeps its 2. Line 11 prints x = 10, y = 2, and line 12 prints x’s address.
Key idea
A pointer is a box that holds a number, an address. *p is the box at that address. Which box a write through p changes depends only on the number in p.
The addresses in the picture are short stand-ins. When I run the program on my machine, line 12 prints a longer number such as 0x7ffd4545a39c, and a different one on every run. The sizes of the boxes are the real ones. Where they sit follows one simple rule that every page of this course uses, so a box has the same address wherever you meet it; a real compiler may order them differently.
The code and its cost
Three pieces of notation do all the work on this page: &, the type int *, and * in an expression.
&x is the address of x: the number of x’s first byte, 0x7fec in the picture. Put & in front of a variable’s name and you get its address.
A pointer has a type that says what kind of box it points to. int *p declares p as a pointer to int, a box for the address of an int. The size of p has nothing to do with the int: an address is 8 bytes on this platform, so sizeof p is 8. A pointer to a double or to a char is also 8 bytes.
In an expression, *p is the box at the address in p. Reading *p reads that box; *p = 9 writes it. Following a pointer this way is called dereferencing it.
Two uses of *
The same symbol does two different jobs, and it helps to keep them apart.
- In a declaration,
*makes the name a pointer.int *p = &x;declares p and puts x’s address into p, not into*p. - In an expression,
*follows the pointer.*p = 9;writes the box p points to.
(Between two values, as in a * b, it still multiplies.) In a declaration the * belongs to the name, not to the type. So what does int *p, q; declare? p is a pointer to int, and q is a plain int. I write one declaration per line, which keeps this clear.
NULL
NULL is an address that names no box. It is defined in <stdio.h> and in other standard headers. A pointer set to NULL points nowhere, so a program checks for NULL before it follows a pointer that might hold it.
Following NULL is a mistake. The C standard leaves what happens undefined, and on this platform the operating system stops the program with a segmentation fault.
What a pointer costs
A pointer costs 8 bytes, whatever it points to. Following one costs one extra step: the machine first reads the address out of p, then reads or writes the box at that address. In the build this course uses (gcc with no optimisation), int x = 5; is one machine instruction, and *p = 9; is two: one loads 0x7fec out of p, the other writes 9 there.
Running it yourself
The code pane shows the whole file. Save it as pointers.c, then build and run it:
gcc -std=c11 -Wall -Wextra -pedantic pointers.c -o pointers
./pointers
Worked example
Here are two pointers and one box. Take a sheet and write down the four boxes a, b, q and r after each line, before you read on. The picture puts a at 0x7fec, b at 0x7fe8, q at 0x7fe0 and r at 0x7fd8. The seven lines are lines 5 to 11 of the program in the replay further down.
int a = 1;
int b = 2;
int *q = &a;
int *r = q;
*r = 7;
q = &b;
*q = *r + 1;
Each line writes exactly one box:
| Line | Code | Box written | New value |
|---|---|---|---|
| 5 | int a = 1; | a | 1 |
| 6 | int b = 2; | b | 2 |
| 7 | int *q = &a; | q | 0x7fec |
| 8 | int *r = q; | r | 0x7fec |
| 9 | *r = 7; | a | 7 |
| 10 | q = &b; | q | 0x7fe8 |
| 11 | *q = *r + 1; | b | 8 |
Line 8 copies the number in q into r. Now both hold 0x7fec: two arrows, one box. r does not point at q; it points where q points.
Line 9 follows r and writes 7 into a. Line 10 changes q alone, to b’s address; r still holds 0x7fec. Line 11 works out the right side first: *r reads a, 7, and 7 + 1 is 8. Then *q = writes 8 into the box at q’s address, b.
At the end a is 7, b is 8, q holds 0x7fe8 and r holds 0x7fec. The program prints a = 7, b = 8. The replay below runs the same lines inside main; step through it and compare it with your sheet.
Two pointers, one box
q and r both hold the address of a, until q moves to b
#include <stdio.h> int main(void){ int a = 1; int b = 2; int *q = &a; int *r = q; *r = 7; q = &b; *q = *r + 1; printf("a = %d, b = %d\n", a, b); return 0;}Output
main has started. Its frame holds a, b, q and r; none has a value yet.
Describe this picture
The 14 lines of two-arrows.c with the line about to run marked, beside the frame of main: three rows of 8 byte cells, higher addresses on top. The top row holds b at 0x7fe8 and a at 0x7fec, an int each; below it q, an int * at 0x7fe0; the bottom row is r, an int * at 0x7fd8. Each box shows its type and name, its address and its value, ”?” until written. The replay runs one line every 1.9 seconds and rests on the end. After line 8 two arrows run from q and from r into a; line 9 sends a dot along r’s arrow and a becomes 7; line 10 swings q’s arrow to b; line 11 sends a dot along q’s arrow and b becomes 8. The output, a = 7, b = 8, appears under the picture when line 12 runs. The transport plays and pauses, steps one line back or forward, and scrubs; a paused step is kept in the page address as two.step.
Try it yourself. Suppose line 10 read r = &b; instead of q = &b;. Then q still points at a and r at b, so line 11 reads b, 2, and writes 3 into a. The program would print a = 3, b = 2.
Where it’s used
Linked structures. Every list and tree in this course is built from pointers. From Singly linked lists (7.1) onwards, each node holds the address of the next one, and walking the structure means following those addresses.
Hardware at fixed addresses. On a microcontroller, the pins, timers and serial ports are controlled through registers at fixed addresses, which the chip’s manual lists. C code reaches a register through a pointer that holds that address.
Handing over large data. A function can be given the address of a large block of data instead of a copy of it: 8 bytes passed, however large the block. Passing pointers to functions (2.2) shows how.
Reference card
| Code | Meaning |
|---|---|
&x | the address of x, a number: 0x7fec in the picture |
int *p; | declares p, a pointer to int; in int *p, q; q is a plain int |
int *p = &x; | declares p and stores x’s address in p, not in *p |
*p | the box at the address in p; *p = 9 writes it, and p is unchanged |
p = &y; | stores a new address in p; only p changes |
NULL | an address that names no box; following it is undefined (here, a segmentation fault) |
sizeof p | 8, for a pointer of any type |
| cost | a pointer takes 8 bytes; following it is one extra read, of the address in p |
Where the * is | What it means |
|---|---|
in a declaration, int *p | p is a pointer to int |
in an expression, *p | the box p points to |
Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.