The confusion
Pointers scare people because the syntax is dense: int *p = &x has three
different operators before you even get to a semicolon. And then there's **p
and p->field and passing &p to a function.
But strip away the syntax and there's one idea: a pointer is a variable that holds an address. That's it. An address is just a number. The number tells the CPU where in RAM to look.
The address-of operator: &
Every variable lives at some address in memory. The & operator gives you that address.
Declaring a pointer
A pointer variable holds an address. The type tells the compiler how to interpret the bytes at that address when you dereference.
What the * means โ it depends on context
The asterisk has two different meanings depending on where it appears, and this trips everyone up:
int *p means "p is a pointer to int."
int **pp means "pp is a pointer to a pointer to int."
The type is not int* โ it's int and the * attaches to the variable.
Writing int* p, q; declares only p as a pointer; q is an int.
Passing a pointer to a function
C passes everything by value. To modify a variable from inside a function, you pass its address. The function receives the address and writes through it.
The NULL pointer
NULL is the zero address โ address 0. No valid object lives at address 0.
It's used as a sentinel to mean "this pointer doesn't point to anything."
Dereferencing NULL crashes your program with a segmentation fault.
Always check before dereferencing a pointer you didn't initialize yourself.
int *p; with no assignment โ p contains whatever bytes
happened to be on the stack. Dereferencing it reads from a random address.
Always initialize pointers: either to NULL or to a real address.
See it in memory
The visualization below shows a pointer variable and the variable it points to, side by side in memory. Watch the address stored in the pointer and the value at that address.