Foundations · Module 1 · Lesson 6 of 7
Your First Pointer: Addresses and Dereferencing
Learn what a pointer is, how to get a variable's address with &, how to declare and dereference a pointer with *, and why pointers matter in C.
By the end you can
- Explain that a pointer stores a memory address, not a value
- Use the & operator to get a variable's address
- Declare a pointer and dereference it with *
- Trace pointer assignment and dereference step by step
Builds on Text in C: Working with Strings.
01 · A question first
You have a variable age = 25. You want another variable that does not store a number, but instead stores *where* age lives in memory. How do you find a variable's address, store it, and read the value at that address?
Make a guess, then step through the trace below to check it.
02 · Watch it run
int age = 25;int *ptr = &age;printf("%d\n", *ptr);- slot 0age: 25(looked at in this step)
- slot 1ptr: uninitialized
- slot 2*ptr: unread
Variable age is created and holds 25 at some memory address.
Machine work: allocate age; store 25
03 · The idea
Start with the problem
Think back to Lesson 3, where we saw that C passes function arguments by value. Suppose you want to write a function swap(a, b) that exchanges the values of two integer variables x = 10 and y = 20 in main, or a function that updates a player's health and score at the same time. If you write:
void bad_swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}Calling bad_swap(x, y) copies 10 into a and 20 into b, swaps the two local copies inside bad_swap, and then destroys a and b when the function returns—leaving x and y in main stuck at 10 and 20.
And remember scanf("%d", &age) from Module 0: how was scanf able to reach into main and fill age when bad_swap could not? Because we handed scanf the memory address where age lives (&age). A variable that stores a memory address is called a pointer, and learning how to hold addresses and follow them is the key that unlocks all of C.
How the idea works
Computer memory (RAM) is a giant numbered row of byte cells. Every byte has a unique numeric address (often written in hexadecimal, like 0x7ffd1040). When you declare int age = 25;, the compiler picks a 4-byte slot in memory—say at address 0x7ffd1040—and writes the number 25 inside those 4 bytes.
A pointer is simply a variable whose value is the address of another variable:
int age = 25;
int *ptr = &age;
printf("%d", *ptr);Step through the trace above to see each line in action:
- Allocating the target variable (Step 1): Line 1 creates
ageand stores25in it. - Taking the address with
&(Step 2): On line 2,&ageevaluates to the memory address whereagelives. The declarationint *ptrcreates a pointer variable namedptr(typed as "pointer toint") and storesage's address insideptr. - Dereferencing with
*(Step 3): On line 3, the expression*ptrdereferences the pointer: it tells the CPU, "Look at the address stored insideptr, travel to that memory location (age), and read theintstored there (25)."
The two jobs of the * symbol
Beginners often get tripped up because C uses the asterisk * for two different jobs depending on where it appears:
- In a declaration (
int *ptr;): The*is part of the type. It announces thatptris not anint, but a pointer to anint(an address where anintlives). - In an expression (
*ptr = 99;orprintf("%d", *ptr);): Onceptralready exists, putting*in front ofptris the dereference operator. It means "theintvariable located at the address stored inptr."
Because *ptr refers directly to the age slot in memory, reading *ptr reads age, and assigning to *ptr modifies age in place:
int age = 25;
int *ptr = &age;
*ptr = 99; /* writes 99 directly into age's memory slot */
printf("%d", age); /* prints 99! */Solving the swap problem with pointers
Now we can write a real swap function that modifies main's variables. Instead of passing copies of x and y, main passes their addresses &x and &y, and swap accepts two int * pointers:
#include <stdio.h>
void swap(int *pa, int *pb) {
int temp = *pa; /* read the int at address pa */
*pa = *pb; /* copy the int at pb into the slot at pa */
*pb = temp; /* write temp into the slot at pb */
}
int main(void) {
int x = 10, y = 20;
swap(&x, &y);
printf("x = %d, y = %d", x, y); /* prints: x = 20, y = 10 */
return 0;
}The NULL pointer: pointing nowhere
What if a pointer does not have a valid variable to point at yet? Never leave a pointer uninitialized (int *p;), because its value is indeterminate. Instead, initialize it to the null pointer constant NULL (defined by standard headers such as <stddef.h>), which compares unequal to any valid pointer to an object or function. Do not assume that a null pointer is represented by the integer bit pattern 0x0:
int *ptr = NULL;
if (ptr != NULL) {
printf("%d", *ptr); /* safe: only dereferences when non-NULL */
}Why it is correct
Notice why a pointer always has a target type (int *, double *, char *) even though every memory address on a 64-bit machine is 8 bytes wide. The address &age only tells the CPU the first byte where age begins in RAM. When you dereference *ptr, the CPU needs to know how many bytes to read starting at that address (1 byte for char *, 4 bytes for int *, 8 bytes for double *) and how to interpret the bits (signed integer vs IEEE-754 floating-point). Strong pointer types ensure *ptr always reads and writes the exact width of the underlying variable.
What it costs
- Space: Pointer sizes are chosen by the target implementation. Several object-pointer types often have the same size on a 64-bit system, but portable code should use
sizeofrather than assume 8 bytes. - Time: Taking an address is usually inexpensive, but dereferencing can involve cache misses, address translation, or faults. Passing a pointer can avoid copying a large object, but it does not make access free.
Pitfalls and variations
| Mistake | What goes wrong | Fix |
|---|---|---|
int *p; *p = 10; | Uninitialized ("wild") pointer writes 10 to a random address and crashes | Point p at a valid int variable (int x; int *p = &x;) before dereferencing |
int *p = NULL; printf("%d", *p); | Dereferencing NULL triggers an immediate segmentation fault | Always guard nullable pointers with if (p != NULL) |
Declaring int* p1, p2; | Only p1 is a pointer (int *); p2 is a plain int! | Put the * on each pointer variable: int *p1, *p2; |
Where it is used
Pointers are the backbone of systems programming in C: they enable functions to write back multiple outputs (scanf, swap), pass large buffers without copying, allocate dynamic memory at runtime (malloc), and link nodes together into trees, graphs, and operating system kernel queues.
You now have all the core building blocks of C programming. In the next module, you will look directly under the hood—tracing how the compiler turns your source code into an ELF binary, how integers and fixed-width types are encoded in binary, how stack frames are laid out in memory, and how pointer arithmetic powers high-performance systems code.
04 · Prove it
Question 1 of 3 · answer from memory
What does &age give you?
05 · Practice
Asked in interviews
Something wrong with this lesson? Report a problem