
Ask a group of first-year students which C topic scares them the most, and most of them will give the same answer: pointers.
That is a pity, because the idea behind pointers is simple. Once it "clicks", a lot of C suddenly makes sense — why scanf needs an &, how arrays really work, and how a function can change your variables.
In this article we will build the idea step by step, with small programs you can run yourself.
First, forget about pointers. Think about houses.
Imagine a street where every house has a number: house 101, house 102, house 103 …
- The house is where something lives.
- The house number tells you where to find it.
Computer memory works the same way. It is a very long street of tiny boxes called bytes, and every byte has a number called its address.
When you write
int marks = 85;
C reserves a few bytes (usually 4 for an int), puts 85 in them and remembers where they are. The variable marks is the house; its address is the house number.
The & operator: "what is the address of …?"
You can ask C for the address of any variable with &:
#include <stdio.h>
int main(void)
{
int marks = 85;
printf("value = %d\n", marks);
printf("address = %p\n", (void *) &marks);
return 0;
}
The output looks something like this:
value = 85
address = 0x7ffd5e3c
The address will be different on your computer and may change every time you run the program. That is normal — the operating system decides where your program lives. What matters is that every variable has an address.
%pis the format for printing addresses, and the(void *)in front is the correct way to pass an address to it.
So what is a pointer?
A pointer is a variable that stores an address. That's it.
int marks = 85;
int *p = &marks;
Read the second line slowly:
int *p— "p is a pointer to an int" (it will hold the address of anint)= &marks— "store the address of marks in it"
Now p "points to" marks. If marks lives at house number 0x7ffd5e3c, then the value inside p is 0x7ffd5e3c.
The * operator: "go to that address"
Having an address is only useful if you can go there. That is what * does when you use it in an expression:
#include <stdio.h>
int main(void)
{
int marks = 85;
int *p = &marks;
printf("%d\n", *p); // go to the address in p and read: 85
*p = 90; // go to the address in p and write 90
printf("%d\n", marks);
return 0;
}
Output:
85
90
Look at the last line. We never wrote marks = 90, yet marks changed. We changed it through the pointer. *p and marks are two names for the same box.
Here is a table worth remembering:
| Expression | Meaning | Value in our example |
|---|---|---|
marks |
the variable | 85 |
&marks |
its address | 0x7ffd5e3c |
p |
the pointer (an address) | 0x7ffd5e3c |
*p |
the value at that address | 85 |
Wait — why does * mean two different things?
This confuses almost everyone, so let's clear it up:
- In a declaration,
*means "this variable is a pointer":int *p; - In an expression,
*means "follow the pointer":*p = 90;
Same symbol, two jobs. After a little practice you will read them without thinking.
Why pointers are useful: a swap that actually works
Here is a classic exam question. Does this function swap two numbers?
void swap(int a, int b)
{
int t = a;
a = b;
b = t;
}
Call it with swap(x, y) and print x and y afterwards — nothing has changed!
The reason: C always passes copies to functions. a and b are new variables that receive copies of x and y. The function swaps the copies, and the copies disappear when the function ends.
The fix is to send the addresses:
#include <stdio.h>
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
int main(void)
{
int x = 1, y = 2;
swap(&x, &y);
printf("x = %d, y = %d\n", x, y); // x = 2, y = 1
return 0;
}
Now a points to x and b points to y, so writing *a writes x itself.
And now you also know why scanf("%d", &age) needs the &: scanf must store a number in your variable, so it needs to know where the variable lives.
Pointers and arrays are close friends
When you use the name of an array in an expression, C turns it into the address of its first element:
int a[5] = {10, 20, 30, 40, 50};
int *p = a; // same as &a[0]
printf("%d\n", *p); // 10
printf("%d\n", *(p + 2)); // 30
p + 2 does not add 2 bytes. It moves 2 elements forward — 8 bytes for 4-byte ints. C does the multiplication for you. In fact, a[2] is defined as *(a + 2).
NULL: a pointer that points nowhere
Sometimes a pointer has nothing to point to yet. Give it the special value NULL:
int *p = NULL;
if (p != NULL)
{
printf("%d\n", *p);
}
NULL can be tested. A pointer that was never given a value, on the other hand, contains garbage — a random address — and is called a wild pointer.
Four mistakes that crash programs
1. Using a pointer before giving it an address
int *p;
*p = 5; // writes to a random place in memory!
Always point it at a real variable (or at memory from malloc) first.
2. Forgetting the * for the second pointer
int *p, q; // p is a pointer, but q is a plain int!
int *p, *q; // both are pointers
3. Following a NULL pointer
int *p = NULL;
printf("%d", *p); // crash: "Segmentation fault"
4. Returning the address of a local variable
int *make(void)
{
int n = 42;
return &n; // n disappears when make() returns!
}
The caller receives an address where nothing valid lives any more — a dangling pointer.
Watch it happen
Pointers are much easier to understand when you can see memory. In our free interactive lab you can step through these programs line by line and watch each variable's address, the arrow from a pointer to its target, and what happens with NULL and dangling pointers:
- Pointer basics —
&,*,NULL - Pointers and functions — the working swap
- Pointers and arrays —
p + 1,p[i]
Quick summary
- Every variable has an address.
&xgives the address ofx.- A pointer is a variable that stores an address:
int *p = &x; *pfollows the pointer to the value.- Functions get copies; pass an address when the function must change your variable.
- Give every pointer a real address or
NULLbefore using it.
Practice
- Declare a
doubleand a pointer to it, and change the value through the pointer. - Write
void addTen(int *n)that adds 10 to the caller's variable. - Write
void minMax(int a, int b, int *min, int *max)that returns two results. - Print
a,&a[0]and&a[1]for anintarray and look at the difference between the addresses.
Pointers are not magic — they are house numbers. Practise with small programs, draw boxes and arrows on paper, and they will soon feel natural.