What Are Pointers and How Do I Use Them?
Addresses, dereferences, and the most powerful feature in C
After this lesson, you will be able to:
- Explain the difference between a variable’s value and its memory address
- Declare pointer variables and assign them using the
&(address-of) operator - Dereference a pointer with
*to read and write the value at the pointed-to address - Distinguish between
*in a declaration (pointer-to) and*in an expression (dereference) - Trace pointer code using box-and-arrow memory diagrams
Pulling Back the Curtain
In Java, when you write ArrayList<Integer> list = new ArrayList<>(), the variable list holds a reference — an invisible pointer to the object on the heap. You never see the address, never manipulate it directly, never think about it.
C rips that curtain away. In C, you can see the address of any variable, store it in another variable, and follow it to read or modify the original. This is what a pointer is — a variable that holds a memory address.
Pointers are what make C powerful enough to build operating systems. They’re also what make C dangerous enough to crash them.
Addresses, Pointers, and Dereferencing
Every Variable Has an Address
Every variable lives somewhere in memory. The & operator gives you that address:
int x = 42;
printf("Value of x: %d\n", x);
printf("Address of x: %p\n", (void *)&x);
Value of x: 42
Address of x: 0x7ffd5e8a3c2c
That hex number is where x lives in memory. It’s a real location on your computer’s RAM.
Declaring Pointers
A pointer variable stores an address. Declare it with *:
int *p; // p is a pointer to an int
double *q; // q is a pointer to a double
char *s; // s is a pointer to a char
Key Insight: Read pointer declarations right-to-left:
int *pmeans “p is a pointer to an int.” The*in the declaration means “pointer to.” This is different from the*used for dereferencing (which comes next).
Making a Pointer Point to Something
Use & to get a variable’s address, then assign it to a pointer:
int x = 42;
int *p = &x; // p now points to x
In memory:
Variable Address Value
┌──────┐
│ x │ 0x1000 → [ 42 ]
└──────┘
┌──────┐
│ p │ 0x1008 → [0x1000] ──→ points to x
└──────┘
p doesn’t contain 42. It contains the address where 42 lives.
Dereferencing: Following the Arrow
The * operator (dereference) follows the pointer to access the value it points to:
int x = 42;
int *p = &x;
printf("p holds address: %p\n", (void *)p); // 0x1000
printf("*p follows it: %d\n", *p); // 42
You can also write through a pointer:
Predict: What do you think this code prints? If
ppoints tox, and we write99through*p, what happens tox? Work it out before reading the answer.
*p = 99; // Changes x through the pointer
printf("x is now: %d\n", x); // 99!
Key Insight:
*pon the left side of=writes to the location p points to.*pon the right side reads from it. The pointer itself (p) holds the address; the dereferenced pointer (*p) accesses the value at that address.
x after line 4?int x = 10; // line 1int *p = &x; // line 2*p = *p + 5; // line 3x = *p + x; // line 4*p = *p + 5 writes 15 to x (since p points to x). On line 4, *p is 15 and x is also 15 (same memory), so x = 15 + 15 = 30. The key insight: *p and x refer to the same location, so changing one changes the other.
The Two Faces of *
This trips everyone up: * means two different things depending on context:
| Context | Meaning | Example |
|---|---|---|
| Declaration | “pointer to” | int *p; — p is a pointer to int |
| Expression | “dereference” | *p = 42; — write 42 to where p points |
From Java: Java references are invisible pointers. When you write
obj.method(), Java automatically dereferences the reference to find the object. In C, you do this explicitly:*pfollows the pointer,p->fieldfollows a struct pointer. The mechanism is the same — C just makes you do it by hand.
NULL Pointers
A pointer that points to nothing should be set to NULL:
int *p = NULL; // Points to nothing
Dereferencing a NULL pointer crashes your program (segmentation fault). Always check:
if (p != NULL)
{
printf("Value: %d\n", *p);
}
Common Pitfall: An uninitialized pointer contains a garbage address — it points to some random location in memory. Dereferencing it might crash, or worse, silently corrupt data. Always initialize pointers to either a valid address or
NULL.
NULL pointer?Why does this matter?
NULL pointer dereferences are one of the most common crashes in C programs. Every pointer you receive from a function — especially calloc, malloc, and fopen — could be NULL. Checking before dereferencing is a habit that prevents hours of debugging segfaults.
Memory Diagrams Are Your Best Tool
Draw these for every pointer problem:
int a = 10; int b = 20; int *p = &a;
┌───┐ ┌───┐ ┌─────┐
│ a │ = 10 │ b │ = 20 │ p │ ──→ a
└───┘ └───┘ └─────┘
*p = 30; // a is now 30
p = &b; // p now points to b
*p = 40; // b is now 40
Final: a = 30, b = 40
The Trick: When pointer code confuses you, draw boxes. One box per variable, arrows from pointers to what they point to. Trace each line of code by updating the diagram. This technique works for every pointer problem you’ll encounter.
a and b?int a = 10, b = 20;int *p = &a;*p = b;p = &b;*p = a;*p = b writes 20 to a (p points to a), so a = 20. Then p = &b redirects p to b. Then *p = a writes a's current value (20) to b, so b = 20. Both end up 20 — this is NOT a swap. Many students expect option C, but they forget that *p = b already changed a to 20 before p was reassigned.
Quick Check: What's the difference between p and *p?
p is the pointer itself — it holds a memory address. *p dereferences the pointer — it accesses the value stored at that address. If p points to x, then *p gives you the value of x.
Quick Check: What happens if you dereference an uninitialized pointer?
Undefined behavior. The pointer holds a garbage address, so dereferencing it reads from or writes to a random memory location. This might crash (segfault), corrupt data, or appear to work — making it one of the hardest bugs to find.
Quick Check: After int x = 5; int *p = &x; *p = 10;, what is the value of x?
x is 10. The pointer p points to x, so *p = 10 writes 10 to the memory location where x lives, changing x indirectly.
Pointers Unlock Everything
You now understand the core mechanism: & gets an address, * follows it. That’s the foundation for everything in Series 3 — and a lot of what comes after.
Next lesson: pass-by-pointer. Remember the swap function that didn’t work in Lesson 2.9? With pointers, you can finally fix it. Pointers let functions modify their caller’s variables — the missing piece from Series 2.
Big Picture: Pointers are why C can build operating systems. They give you direct access to memory, let functions modify external data, enable dynamic memory allocation, and connect to hardware. Every remaining topic in this course — dynamic arrays, structs, linked lists, file I/O, system calls — builds on what you learned in this lesson.