If there is one topic that throws students into crisis at their first C programming exam, it is pointers. I have seen people who wrote loops and functions without any trouble freeze completely in front of an asterisk. And almost always it is not their fault: it is the fault of how they are explained.
The pointer is presented as an abstract monster — “a variable that contains an address” — and then the student is expected to understand int **p on the spot. It doesn’t work like that. In this guide I explain pointers and memory management the way I explain them in lessons: starting from a concrete mental model, then going up in complexity one piece at a time.
First of all: what memory is
You can’t understand pointers if you aren’t clear about what a program’s memory is. Imagine it as a very long row of numbered boxes, one next to the other. Each box holds a byte and has a number that identifies it: that number is its address.
When you write int x = 42;, the compiler does three things:
- It reserves a few consecutive boxes (usually 4, for an
int). - It writes the value 42 into them.
- It notes that that group of boxes is called
x.
The name x is convenient for you, but the computer thinks in addresses. And this is where the pointer comes in.
A pointer is just an address
A pointer is a variable that, instead of holding a “normal” value like 42, holds the address of another box. That’s all. There is no magic.
int x = 42;
int *p = &x; // p holds the address of x
Two symbols to keep well apart, because half the confusion starts here:
&xmeans “the address of x” (the address-of operator).*pmeans “the value held at the address stored in p” (the dereference operator).
So after the two lines above, *p is 42. And if you write *p = 7;, you are modifying x through the pointer: now x is 7. You haven’t touched x by name, but you have reached its box through its address.
The advice I always give: read int *p as “*p is an int”. That is: when you dereference p, you get an integer. This helps you not to confuse the declaration with the use.
Why pointers exist
At this point many students think: “OK, but why complicate my life? If I want 42, I write 42”. A legitimate question. Pointers are for three concrete reasons.
First: modifying a variable inside a function. In C, parameters are passed by copy. If you write a function double_it(int n) and inside you do n = n * 2, the caller sees no change, because you have modified a copy. To really modify the original variable you have to pass its address:
void double_it(int *n) {
*n = *n * 2;
}
// call: double_it(&x);
Second: handling arrays and strings. In C the name of an array is in effect the address of its first element. Walking through an array with pointers is the way the language is designed to work.
Third: allocating memory when you don’t know in advance how much you’ll need. And this is where the part that frightens most people comes in: malloc and free.
Stack and heap: the two memories
Your program uses memory in two different ways, and it is essential to understand the difference.
The stack is where local variables live. When you enter a function, its variables are created there; when the function ends, they are destroyed automatically. Fast, orderly, but with two limits: space is limited and a variable’s life ends with the function.
The heap is the memory you manage by hand. You ask for a piece when you need it, with malloc, and you are responsible for giving it back, with free. It lives until you free it, even beyond the function that created it. It is powerful and it is exactly the reason you have total control in C — but also why in C you shoot yourself in the foot more easily than in Python or Java.
int *v = malloc(10 * sizeof(int)); // I ask for space for 10 ints
if (v == NULL) { /* handle the error! */ }
v[3] = 99; // I use the array
free(v); // I give the memory back
v = NULL; // good habit
Note sizeof(int) instead of writing 4 by hand: it makes the code portable and readable. And note the v == NULL check: malloc can fail, and ignoring it is one of the first mistakes I correct.
The four mistakes I see in every session
After a few years of lessons, memory mistakes are almost always the same four. If you learn to recognise them you are already halfway there.
-
Memory leak. You ask for memory with
mallocand forget tofree. The program keeps running but consumes more and more RAM. In an exam exercise it doesn’t crash, so it goes unnoticed — until you use a tool like Valgrind that rubs it in your face. Rule: everymallocmust have itsfree, and while you write the first, already think about where you will put the second. -
Dangling pointer. You do
free(v)but keep usingv. The memory is no longer yours, and what you read or write is undefined behaviour: sometimes it works, sometimes it crashes, sometimes it silently corrupts other data. That is why afterfreeit is worth settingv = NULL: at least a later use fails obviously instead of subtly. -
Uninitialised pointer. You declare
int *p;and then do*p = 5;without ever having madeppoint to anything. You are writing into a random box of memory. Always initialise, even just toNULL. -
Using a local variable after the function ends. You return the address of a local variable (
return &x;inside a function): that box on the stack no longer exists as soon as the function terminates. If you need something that outlives the function, it goes on the heap.
The common thread of all four: in C nobody protects you. The compiler often doesn’t complain, the program starts anyway, and the bug shows up hours later at a distant point. Learning to reason about the ownership of memory — who owns it, who frees it, how long it lives — is the real skill to acquire.
How to really study pointers
You read the theory of pointers in half an hour. The problem is that reading isn’t enough: you have to see them move. Here is how I work with my computer science students, and how I recommend you study on your own.
Draw the memory on paper. For each line of code, draw the boxes with their addresses and the pointer arrows. It looks like child’s play, but it is exactly what distinguishes those who “understand” pointers from those who suffer them. When you see *p = *q, on the sheet you follow the arrow of q, take the value, and write it where p points. If you can do it on paper, you can do it in your head.
Use a step-by-step debugger. With gdb (or VS Code’s built-in debugger) you run the program one line at a time and watch how the addresses and values change. Seeing p hold 0x7ffe... and *p change is more instructive than ten pages of a book.
Always start from one level of indirection. First you master int *p. Only after that do you move to int **p (pointer to pointer), which is needed for dynamic matrices and for modifying a pointer inside a function. Skipping this step is the perfect recipe for confusion.
Run Valgrind on your exercises. It is the tool that tells you exactly where you have a leak or an illegal access. Get into the habit of launching it on every program with malloc: in the exam it usually isn’t needed, but it builds an instinct that is worth gold.
Realistic timescales
How long does it take to be solid on pointers? If you start with the basics of C already in place (variables, loops, functions), with targeted work two to three weeks are enough: one for the mental model and simple pointers, one for stack/heap and dynamic allocation, one for dynamic arrays, strings and double pointers.
It is not a topic you “suddenly understand”: it is a muscle you train by doing exercises and getting things wrong. Whoever tells me “I understand the theory but I get the exercises wrong” almost always hasn’t yet drawn enough memory on paper.
How we work in lessons
In my lessons on pointers I always start from your code, not from abstract textbook examples. If you have an exam exercise or a project that segfaults (the infamous segmentation fault), we open it together, run it in gdb and follow it line by line until we see where and why the pointer goes where it shouldn’t. It is the quickest way to make it click.
I am an engineer from the Politecnico di Milano and I have been tutoring university students in C, algorithms and data structures for years: programming and fundamentals exams are among those I follow most often. The average grade of my students is 8.2/10, with over 215 students followed and 97% of tests passed — numbers that come precisely from working on the points where someone gets stuck, instead of redoing the theory they already know.
The rate overview (single lesson or 10-hour package) is always on the pricing page: for a topic like pointers, a couple of targeted lessons are often enough to unblock the rest of the course.
Once pointers are clear, you need two more topics: pass-by-reference in functions and, at a more advanced level, threads and semaphores. For programming classes in C++ see classes and objects, and for the running time of an algorithm the big O notation. If the C course goes together with calculus and linear algebra, practise with the university exam practice tests.
If you are also preparing for an admission exam with a logic or computer science part, look at the page dedicated to admission tests. And if you want to understand what you are stuck on with no commitment, book a free introductory call: send me the exercise that drives you mad and we’ll talk about it. Often in twenty minutes you can tell whether it is a problem with pointers or, as happens more often than you think, with not yet having brought into focus what memory really is.