Pointers and addresses
Every value lives at an address. A pointer holds one, the star follows it to the value, and that is how a function changes the caller's variable.
Everything has an address
Each variable sits somewhere in memory, and that place has an address, a number like a house number. You can ask for it with the & operator: &x is the address where x lives.
A pointer is a variable that holds an address. You declare one with a star between the type and the name: an int pointer is written with the int, then a star, then the name. It does not hold a number yet, just the place to find one.
Following the pointer
The star has a second job. In front of a pointer it means the value at this address: the star followed by the pointer name reaches what is stored there. Following is called dereferencing.
So p = &x; points p at x, and writing through the star, as in the star p set to 99, puts 99 into x through the pointer. Reading and writing through a pointer reach the original variable, not a copy.
Why a function needs one
Functions receive copies of their arguments. That is a problem when you want a function to change the caller's variable. Passing the address instead lets the function reach the original.
The classic example is swapping two values. A swap that takes two whole numbers changes only its own copies. A swap that takes two addresses, and swaps the values at those addresses, changes the real ones.
A pointer is typed for a reason
A pointer stores an address, but its type says what lives there. int *p steps by the width of an int when you add one, while char *p steps one byte. That is why p + 1 means "the next int", not "the next byte".
An array name in an expression becomes a pointer to its first slot. values[i] and *(values + i) are the same access written two ways, which is why a function taking int * can walk an array it did not allocate.
NULL, and the lifetime of an address
NULL is the address that points at nothing, and it is the one value you can test before following a pointer. An uninitialised pointer is not NULL; it holds an arbitrary address, so reading through it is undefined behaviour from the first use.
An address is valid only while its object lives. Returning &local from a function hands back a pointer into a frame that has already gone, and the caller may read garbage. Point at memory that outlives the call: a caller's variable, a static, or a block from malloc.
A declaration is not an expression
The star in int *p belongs to the variable, not to the type: it says p is a pointer to int. That matters in a line with several names. int *a, b; declares a pointer called a and an ordinary int called b, because the star attaches only to the name that follows it. Writing one declaration per line avoids the confusion entirely.
Reading a declaration from the name outward is the habit that scales. int *p reads as p is a pointer to an int; char **names reads as names is a pointer to a pointer to a char. The same symbols mean follow when they appear in an expression, but in a declaration they describe the shape of the variable.
An array name is not a pointer variable
An array name turns into a pointer to its first slot when you use it in an expression, but it is not itself a variable that holds an address. You cannot assign a new address to it, and sizeof reports the whole block rather than the pointer. A real pointer can be reassigned and measures one machine word.
That difference explains the parameter rule from the arrays chapter. Inside a function that takes int values[], the parameter really is a pointer, so the block size is gone. The name decayed at the call, and only the caller still knows how many slots there are.
Three pointer mistakes cause most crashes: following a pointer before it is set, returning the address of a local, and confusing *p++ with (*p)++. The first reads an arbitrary address, the second reads a dead frame, and the third moves the pointer instead of changing the value it points at.
Try these next
- Write a function that takes
int *and doubles the value it points at; call it on a local and print the local afterwards. - Walk an array with a pointer from the first slot to one past the last, summing as you go.
- Draw the two variables and their arrows for
int x = 5; int *p = &x;before you write any code.
Point at a value
#include <stdio.h>
int main(void) {
int x = 10;
int *p = &x;
*p = 42;
printf("x is now %d\n", x);
return 0;
}Swap through pointers
Run it to see the two values change for real. Then change swap to take int values instead of pointers and watch it fail to swap anything.
Walk an array with a pointer
Run it, then change the array and check the total by hand. Then replace the pointer loop with `values[i]` and confirm the answer is the same.
A pointer that points at nothing, or at memory that is gone, is the most common crash in C. Before you follow a pointer, make sure it holds an address that is still alive.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What does & give you in front of a variable?
