Pointers
Addresses, the * and & operators, and why pointers are the soul of C
This is the chapter people are warned about. "Pointers are hard." "That's where C gets weird." They are right that pointers are where C reveals what is really going on. They are wrong that it has to be confusing.
The trick is to keep the picture in front of you at all times. Every pointer is just a box that holds an address. Once you draw the boxes, everything else falls into place.
What a pointer is
Every byte of RAM has an address — a number. A pointer is a
variable whose value is one of those addresses. We write the type of
a pointer-to-int as int *:
int n = 42; // an ordinary int
int *p = &n; // a pointer that holds the address of nTwo new operators appeared:
&n— the address-of operator. Read it as "the address of n".int *p— the type "pointer to int". Read the asterisk in declarations as "is a pointer to".
To read or write through the pointer, use * (the
dereference operator):
*p // the value at the address p holds, i.e. n
*p = 100; // store 100 into the memory p points at, i.e. into nThe exact addresses printed will differ every run. That's fine — we care about the relationships, not the specific numbers.
The two operators, side by side
The most common beginner trap is reading the * in a declaration
the same way you read it in an expression. They mean different
things:
| Context | Meaning |
|---|---|
int *p; | declaration: p is a pointer to int |
*p | expression: the value at the address stored in p |
&n | expression: the address of n |
int *p = &n; | declare p, initialize it with the address of n |
Once you internalize that, half of pointer confusion vanishes.
Pictures, always pictures
Let's trace a tiny program in pictures.
int n = 7;
int *p = &n;
*p = *p + 1;After line 1: a box n exists, containing 7.
+-----+
| n |
+-----+
| 7 |
+-----+After line 2: a new box p exists, containing the address of
n:
+-----+ +-----+
| p | ----> | n |
+-----+ +-----+
|0x100| | 7 |
+-----+ +-----+After line 3: *p reads the value at n (7), adds 1, and
writes 8 back into the same address:
+-----+ +-----+
| p | ----> | n |
+-----+ +-----+
|0x100| | 8 |
+-----+ +-----+The value of p itself never changed. Only the value of the thing
it pointed at changed.
Pointers as function parameters
Recall that C passes arguments by value: a function gets a copy of each argument. If you want a function to modify a caller's variable, you pass a pointer to it.
The parameter x is of type int *. *x is the int it points at.
(*x)++ increments that int. The parentheses matter: *x++ would
parse as *(x++), which is a different (and much more confusing)
operation.
This pattern — "pass &variable so the function can change it" — is
how every scanf-style API works.
Returning two values from a function
A function can return only one value, but it can also write to several output parameters via pointers:
The null pointer
A pointer that points to nothing valid should be set to NULL:
int *p = NULL;Dereferencing a NULL pointer is undefined behavior, but on most
systems it crashes loudly (a "segmentation fault"), which is much
better than silently corrupting memory. Check for NULL before
using a pointer that might be unset:
if (p != NULL) {
*p = 5;
}A common idiom is if (p) / if (!p), since NULL is zero (false).
What happens if you forget to initialize a pointer
An uninitialized pointer holds whatever bits were in those bytes already — an essentially random address. Dereferencing it could crash, or could silently overwrite some unrelated piece of memory.
int *p; // p holds garbage
*p = 5; // writing to a random address — very badAlways initialize. Either point at something real with &x, or
set to NULL to mark "not yet pointing at anything".
Pointer types matter
A char * and an int * are different types — they advance through
memory by different amounts when you do arithmetic on them, and they
read different numbers of bytes when you dereference. Mixing them
without intention is a bug.
int n = 42;
int *pi = &n;
char *pc = (char *)&n; // a deliberate cast — looking at n one byte at a timeWe'll use this kind of cast deliberately in the chapter on memory layout. For now, stick to pointer types that match what you're pointing at.
A walk through a function call
Here is a tiny but complete example that uses pointers to swap two variables:
Trace it with pictures, before and after each line of swap. You
should see that tmp (a local variable inside swap) holds x's
value, then x is overwritten by y, then y is overwritten from
tmp. The pointers a and b give swap access to main's
variables without copying them.
Challenge: in-place addition
Write a function void add_to(int *value, int delta) that adds delta to whatever value points at. main uses it like this:
int x = 10;
add_to(&x, 7);
printf("%d\n", x); // should print 17
The program must print exactly 17 and nothing else.
Given:
int n = 5;
int *p = &n;
*p = 10;
What is the value of n afterwards?
5
The address of n.
10
Undefined, because p was reassigned.
What is the type of the expression &x if x is declared as double x;?
double
int *
void *
double *