Pointers in C
Introduction
Every variable in your program lives somewhere in memory — at a numbered address, like a house on a street. A normal variable (int a) holds a value (for example 80). A pointer (int *p) holds the address of where some value lives — like writing down "house number 42" instead of storing the furniture itself.
Pointers are not an academic trick. In embedded systems you use them to talk to hardware registers (fixed addresses in the datasheet), walk through arrays, build linked lists, and pass data to functions without copying large blocks. This article explains pointers step by step, including what goes wrong when you misuse them.
Memory addresses in one picture
Picture RAM as a long row of numbered mailboxes. Each mailbox holds one byte (for simplicity). Four consecutive mailboxes might hold one 32-bit int.
1 2 3 4 | |
The address of a is 9000 (shown as 0x... in hex on real systems). A pointer to a stores that number so you can find a later.
Declaring pointers
1 2 3 | |
The * in a declaration means "pointer to". Spacing is style: int* p and int *p mean the same thing.
In plain terms
If int a is a teddy bear in a box, then int *p is a note that says which shelf the box is on. The note is not the bear — it tells you where to find the bear.
Two operators: & and *
| Operator | Name | What it does |
|---|---|---|
& |
Address-of | "Where does this variable live?" |
* |
Dereference | "What value lives at this address?" |
1 2 3 4 | |
After that:
pand&aare the same address*pandaare the same value (80)- Changing
*p = 99also changesato99
Complete example
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
Use %p with a (void *) cast to print addresses portably.
The uninitialized pointer — writing into thin air
1 2 3 4 | |
Here p was never assigned. It contains garbage — some random address. *p = b tries to write 160 into someone else's memory. The program might crash, corrupt another variable, or appear to work until something unrelated breaks.
Always set a pointer before use:
p = &a— point at existing variablep = NULL— point at nothing (safe to test, unsafe to dereference)p = malloc(...)— point at heap block (see Dynamic memory and I/O)
Pointer arithmetic — why p++ is not always +1
When you add 1 to a pointer, C moves forward by one element, not one byte:
1 2 | |
That is why you can walk an array by incrementing a pointer — the type tells the compiler the step size.
1 2 3 4 5 | |
The array name row_int is the address of element 0. That link between arrays and pointers is developed further in Pointers in depth.
Pointers and strings
A string literal like "test" is stored in read-only memory. A char * can point at the first character:
1 2 3 4 5 | |
C strings end with a hidden '\0' (zero) byte. That is how printf knows where to stop.
Important: char *p = "test" must not be used to modify the string — the literal is read-only. For a mutable string:
1 | |
Endianness — reading an integer byte by byte
An int uses several bytes in memory. Endianness is the order those bytes are stored.
1 2 3 4 5 | |
On a typical little-endian PC or ARM Cortex-M, you see 93 BE 27 FA — least significant byte first. On big-endian systems you would see FA 27 BE 93.
In plain terms
Imagine writing the number 1234 on paper. Little-endian writes digits 4-3-2-1 in memory; big-endian writes 1-2-3-4. The number is the same; only the storage order differs. When two devices talk over a bus, they must agree on the order.
This matters when you parse binary sensor frames or write multi-byte registers. See Data representation.
Why embedded engineers care about pointers
Hardware registers — The datasheet says "GPIO output data register at offset 0x14". You access it through a pointer:
1 2 | |
volatile tells the compiler the value can change outside the program (the hardware changes it). Never omit volatile for MMIO.
Efficiency — Passing a pointer to a large struct costs 4 bytes; passing the whole struct copies every field.
Data structures — Linked lists and queues chain nodes through pointers — see Data structures in C.
DMA — The DMA controller needs the start address of your buffer — a pointer.
Relevant topics
Starting points
- Draw mailboxes on paper for
int a = 80andint *p = &a— label address and value. - Predict endianness output before running the byte-print program.
- Run with AddressSanitizer:
gcc -fsanitize=addresson the uninitialized pointer example. - Find one
volatile ... *register definition in your MCU header file.
Focus points
- Initialize every pointer before
*p. NULLmeans "points nowhere" — check before dereference.- String literals are read-only when pointed to by
char *. - Endianness bites when two systems exchange binary data.
Key points
- A pointer stores an address;
&gets it,*follows it. - Dereferencing a bad pointer causes crashes or silent corruption.
- Pointer arithmetic moves by
sizeof(type), not always one byte. - Endianness determines byte order inside multi-byte values.