Pointers. Here we are at last. Let me give it to you straight: this chapter won't be a walk in the park. Oh no, not at all.
We're still a ways from the end of this C programming journey, and yet I can already tell you — this is the chapter that trips people up the most. It's a true turning point: from here on, we dive into what are called pointers in C.
A lot of beginner programmers hit a wall when they encounter pointers. More precisely, they struggle with two things:
- How they work
- What they're even good for
I'll do my absolute best to walk you through all of it, slowly and steadily. Don't rush — it's the quickest way to get burned.
Stay sharp and hang on tight: now's the time to crank up the focus by a factor of four. Those who survive this chapter will earn a golden ticket to the wonderful stuff that's coming next. :)
Of Pokémon and Major Headaches
One of the biggest issues with pointers — aside from being tricky to grasp at first — is that it's not immediately obvious why you'd need them.
I mean sure, I could just tell you, "Pointers are absolutely essential, we use them all the time, they're super useful, I swear!" But I can already see your skeptical faces.
So to avoid that, let me present a problem you won't be able to solve without using pointers. This'll be the running theme of this chapter. We'll revisit it at the end, and by then, you'll know exactly how to crack it.
Here's the setup: I want to write a function that returns two values.
"Impossible!" you say?
And you're right — normally, a function can only return one value:
int function() { return myNumber; }
If you write int, the function returns an integer using the return statement.
You can also write a function that returns nothing, using the void keyword:
void function() { // ... }
But returning two values at once? Nope. No way to do two returns or anything like that.
int brokenFunction(int a, int b) { // return a+2, b-1; (NOPE!) }
Now let's say I ambitiously decide to recreate Pokémon. Two Pokémon face off, and the first to hit 0 HP faints. In addition to dealing 10 damage, Bulbasaur's iconic "Poison Fang" attack poisons the enemy Pokémon too. Let's attempt to recreate that.
To model this attack in C, we need to:
- Grab the damage value from Poison Fang (here, it's 10 — hardcoded to keep it simple, even if that's bad practice)
- Subtract that damage from the opponent's HP and store the new HP
- Apply a "poisoned" status to the opponent
So right off the bat, we're dealing with at least two values that need to change: the HP and the status. And to spice things up, let's say they're different types: HP is an int, and status is a char.
All right, let's give it a shot:
#include <stdio.h> #include <stdlib.h>// Function to simulate the player's attack on a wild Pokémon void attackPokemon(int pokeHP, char pokeStatus) { // Deal damage pokeHP -= 10; printf("You dealt 10 damage to Bellsprout! Bellsprout's HP is now %d.\n", pokeHP);
// Apply poison status pokeStatus = 'P'; // P for poisoned printf("Bellsprout is now poisoned! (%c)\n", pokeStatus); }
int main() { int playerHP = 100; int pokeHP = 150; char playerStatus = 03; // ♥ means healthy (ASCII) char pokeStatus = 03; // ♥ means healthy (ASCII)
printf("Battle begins!\n"); printf("Bulbasaur HP: %d (%c), Bellsprout HP: %d (%c)\n\n", playerHP, playerStatus, pokeHP, pokeStatus);
// Player attacks the wild Pokémon attackPokemon(pokeHP, pokeStatus);
// Check post-attack state printf("\nAfter the attack:\nBulbasaur HP: %d (%c), Bellsprout HP: %d (%c)\n\n", playerHP, playerStatus, pokeHP, pokeStatus);
return 0; }
pokeStatus = 80;
which is the same as 'P'.Here's what we get:
Battle begins! Bulbasaur HP: 100 (♥), Bellsprout HP: 150 (♥)You dealt 10 damage to Bellsprout! Bellsprout's HP is now 140. Bellsprout is now poisoned! (P)
After the attack: Bulbasaur HP: 100 (♥), Bellsprout HP: 150 (♥)
Argh, nope nope nope... it didn't work. Not that I was holding my breath or anything (I was).
We should have seen: Bulbasaur HP: 100 (♥), Bellsprout HP: 140 (P)
on that last line.
Actually, it's much simpler. If you read the earlier chapters on functions, you might already have a clue. When you "send" a variable into a function, it makes a copy. So the pokeHP and pokeStatus inside attackPokemon are not the same variables as in main — they're just clones!
Your attackPokemon function is doing its job. Inside it, the variables really do have the right values: 140 and 'P'. We can see that from the printf.
But the moment the function ends — boom — those copies are destroyed. Like we said before, variables created inside a function vanish when that function ends. Your copies of HP and status get deleted.
Back in main, your original pokeHP and pokeStatus are still sitting at 150 and ♥. Epic fail.
void attackPokemon(int h, char s) { h -= 10; s = 'P'; }
with h for HP, s for status. Doesn't matter what you call them — they're just placeholders!Anyway, no matter how you slice it, you can't return two values from a function. You can return one value (using return and setting the return type to int), but that's it.
Now yes, a clever programmer — like you :) — might try to bend the rules using a cryptographic trick:
#include <stdio.h> #include <stdlib.h>int attackPokemon(int pokeHP, int pokeStatus) { pokeHP -= 10; printf("You dealt 10 damage to Bellsprout! Bellsprout's HP is now %d.\n", pokeHP);
pokeStatus = 1; printf("Bellsprout is now poisoned!\n"); int result = 10 * pokeHP + pokeStatus; // sneaky! return result; }
int main() { int playerHP = 100; int pokeHP = 150; int playerStatus = 0; // 0 = healthy int pokeStatus = 0; // 0 = healthy
printf("Battle begins!\n"); printf("Bulbasaur HP: %d (%d), Bellsprout HP: %d (%d)\n\n", playerHP, playerStatus, pokeHP, pokeStatus);
int result = attackPokemon(pokeHP, pokeStatus); // Decode result if (result % 2 == 0) { // Even = no poison } else { pokeStatus = 1; result--; // remove poison marker }
result /= 10; // decode HP pokeHP = result;
printf("\nAfter the attack:\nBulbasaur HP: %d (%d), Bellsprout HP: %d (%d)\n\n", playerHP, playerStatus, pokeHP, pokeStatus);
return 0; }
But as elegant as that little trick is — and I'm sure your math teacher would drool all over it — it just dodges the real issue. Plus, it forces you to convert between char and int, which adds more headaches.
So nope, not the solution. What we really want is to be able to directly modify pokeHP and pokeStatus inside the attackPokemon function — no global variables, no hacks. Just good old clean code. (Global variables, by the way, use the same underlying logic as pointers.)
This is just one example among many of why pointers are super handy. I chose this one because, well, it's pretty fun.
All right! Now we're ready to dive into the real meat of the chapter!
Memory and Addresses
A Quick Recap
Flashback time! Remember that chapter on variables?
Whether your answer's yes or no, I highly recommend going back and rereading the first part of that chapter, called "A World of Memory." Of course, I can't force you to do it... but don't come crying to me later saying none of this makes sense ;)
There was one diagram in there that was super important. Let me bring it back here. It showed how memory works:
Address | Value |
---|---|
0 | ... |
1 | ... |
2 | ... |
3 | ... |
X | ... |
100 | ... |
That's kind of how we represent your computer's RAM (aka random access memory). You read this diagram line by line.
The first line is the very first "cell" of memory. Every cell has a number — it's called an address. (Yes, the vocabulary is super important here!) Your computer's memory is made up of a whole lot of these addresses. They start at 0 and go all the way up to... well, some massive number.
Each address can store a number. One and only one number.
So no, you can't store two numbers in one address.
Your memory is made to store numbers. Just numbers. No letters, no sentences, no little poems. To work around this, we use a table that links numbers to letters. This table says things like "the number 80 represents the letter P." (like we did in our example)
But don't worry — text handling in C is something we'll cover much later. Before we can make sense of that, we need to get a handle on something else: pointers.
Address vs. Value
Let's get back to it — because that's what today's all about.
When you create a variable, say age, and you write this:
int age = 10;
... your program is basically asking the operating system (like Windows) for a little bit of memory. The operating system responds by saying: "Sure! You're allowed to write your number here," and it tells you the exact address.
That's one of the core jobs of an operating system: it allocates memory to your programs. It's the boss — it controls all the programs and makes sure each one uses only the memory it's been assigned.
In fact, memory-related issues are the #1 reason programs crash. If your program tries to access a part of memory it hasn't been given access to, the OS (let's call it that from now on) slams the door shut and instantly kills the program as punishment. ("Who's the boss? I'm the boss!")
The user, on their end, sees a nice little pop-up: "This program will now be closed because it performed an illegal operation" (if it's a minor issue)... Or worse: the dreaded Blue Screen of Death. 😱 But hey, a little memory overreach like this usually won't completely lock up your whole computer.
Anyway, back to our variable age. The value 10 is stored somewhere in memory — let's say at address 5755.
- Here's what happens (this is the compiler's job): the word age in your code gets swapped out for 5755 when the program runs.
- Every time you typed age in your code, it's replaced with 5755, so the computer knows exactly where in memory to look!
- It goes to memory address 5755 and proudly declares, "The value is 10!"
So if you want to get the value of the variable, you just type age in your code. Want to print it out? Easy:
printf("The value of age is: %d", age);
Which shows:
Nothing new there, right?
The Mysterious Address
We know how to display the value of a variable. But... did you know you can also display its address? 😄 ...Oh right, you didn't.
To display the address of a variable, you use %p in printf (the p is for "pointer"). And instead of passing age to printf, you pass its address. To do that, just add an & in front of the variable — remember when I had you do that in scanf a while back, without explaining why? Yep, this is why. ;)
Try this:
printf("The address of the variable age is: %p", &age);
And you'll get something like:
What you're seeing there is the address of the variable age at the exact moment I ran the program on my computer. And yep — it's a number.
0023FF73
is a number, it's just written in hexadecimal instead of the usual decimal system we humans use.
If you replace %p
with %d
, you'll see the number in decimal form (which is easier for us to read). But %p
was made specifically to show addresses, so I usually stick with it.
Without going too deep into it, just so you're not thrown off: the decimal system uses 10 digits: 0 1 2 3 4 5 6 7 8 9
. The hexadecimal system (which computers use often) uses 16 digits: 0 1 2 3 4 5 6 7 8 9 A B C D E F
. Yes, those letters are digits in hex.
Every number in hex can be converted to decimal, and vice versa. In this system: A = 10, B = 11, C = 12... F = 15, 10 = 16, 11 = 17, and so on.
If you've got a calculator (like the Windows calculator in scientific mode), you can do the conversions easily. Just make sure you're in Scientific mode: View > Scientific
. Then click "Hex" and enter the hex number. Click "Dec" to see its decimal equivalent. Works the other way around too.
So yeah, I found out that 0023FF73
is actually 2359155
in decimal. Not exactly life-changing info — but it's fun to understand how it works, right? 😄
If you run this program on your computer, you'll most likely see a different address. It depends on what memory is free, what other programs are running, and so on.
There's no way to predict where your variable will be stored.
Where am I going with all this?
Simple. I just want you to remember this key takeaway:
age | → | displays the VALUE of the variable |
&age | → | displays the ADDRESS of the variable |
When you use age, the computer goes and reads the value stored in memory, and gives it back to you. When you use &age, it tells you where in memory that variable is stored.
Using Pointers to Access Memory
Up until now, we've only been creating variables meant to hold numbers. Now, we're going to learn how to create variables meant to hold addresses. These are what we call pointers.
That's exactly right. But these numbers have a special meaning: they point to the address of another variable in memory.
Creating a pointer
To create a pointer-type variable, you just add the *
symbol in front of the variable name:
int *myPointer;
You could also write:
int* myPointer;
It's the same thing. But the first version is generally preferred. Why? Well, if you want to declare several pointers on the same line, you have to put the asterisk in front of each name, like this:
int *pointer1, *pointer2, *pointer3;
As I've mentioned before, it's important to always initialize your variables right away (for example, setting them to 0). With pointers, this is even more crucial!
To initialize a pointer (give it a default value), we don't typically use the number 0, but rather the keyword NULL (uppercase is essential!):
int *myPointer = NULL;
This gives you a pointer that's been initialized to NULL. That way, as your program runs, you'll know the pointer doesn't yet hold any address.
What does this code do, exactly? It reserves a space in memory, just like a regular variable. But here's the twist: the pointer's value is meant to hold an address. The address... of another variable.
So why not use the address of the variable age? You now know how to get a variable's address (by using &), so let's go:
int age = 10; int *pointerToAge = &age;
- The first line says: "Create an int variable and set it to 10."
- The second line says: "Create a pointer variable and assign it the address of the variable age."
That second line is doing two things at once. If you'd rather break it down, you totally can:
int age = 10; int *pointerToAge; // 1) "I'm creating a pointer" pointerToAge = &age; // 2) "pointerToAge now contains the address of age"
Notice there's no special pointer type like there is int or double.
You don't write:
pointer pointerToAge;
Instead, we use *, but we still write int.
(Okay, hang on tight...)
That's because you have to tell the compiler what type of variable the pointer is going to point to. Since our pointer pointerToAge will hold the address of an int, we declare it as int*. If age had been a double, we'd have declared the pointer as double *myPointer
.
Vocabulary tip: We say that pointerToAge points to the variable age.
Here's a quick visual of what's happening in memory:
Address | Value |
---|---|
0 | 305 |
1 | 4 |
2 | 88 |
3 | 277451 ← pointerToAge |
... | ... |
277451 | 10 ← age |
... | ... |
13812441 | 950 |
In this example, the variable age is stored at address 277451 (and yep, its value is 10), while the pointer pointerToAge is stored at address 3 (I made these numbers up, obviously).
So when the pointer is created, the OS reserves a spot in memory just like it did for age. But this time, the value stored in the variable is special — it's the address of another variable.
Ladies and gentlemen, this right here is the secret sauce behind every C program ever written. We've made it. Welcome to the magical world of pointers!
Nice pun. It won't turn your computer into a coffee machine... not yet, anyway. However, what we do have now is pointerToAge, which contains the address of the variable age.
Let's try printing out the pointer's contents:
int age = 10; int *pointerToAge = &age;printf("%d", pointerToAge);
Output:
277451
Hmm. Makes sense, right? You asked for the value of pointerToAge, and its value is the address of age.
But how can we ask for the value of the variable that lives at the address stored in pointerToAge? Just slap a * in front of the pointer's name:
int age = 10; int *pointerToAge = &age;printf("%d", *pointerToAge);
Output:
10
🎉 YES! We did it!
Using *pointerToAge
, we accessed the value of age through its address.
And if we had written &pointerToAge
, we'd have gotten the memory address where the pointer itself is stored (in this example: 3).
Ah, that question makes me smile for two reasons:
- I knew you were going to ask that. And honestly? Fair point! Right now, the benefits aren't super clear. But stick with me — over the next few chapters, it's all going to make sense. Trust me: this isn't just some weird way to make your life harder. 😄
- I remember exactly how you feel. I was the same way when I first learned C. I know the frustration. I really do. But hey — that's not much comfort, I admit.
Must-know summary
Before we move on, here's what you absolutely must remember. Four simple points:
For a regular variable, like age:
- age means: "Give me the value of the variable age."
- &age means: "Give me the address where the variable age is stored."
For a pointer, like pointerToAge:
- pointerToAge means: "Give me the value of the pointer" (i.e., an address).
- *pointerToAge means: "Give me the value of the variable at the address stored in pointerToAge."
Memorize these. Test them out. Try things.
Just be careful not to confuse what the asterisk means! When you're declaring a pointer:
int *pointerToAge;
The *
just says, "Hey, this is a pointer."
But when you later use that pointer in your code:
printf("%d", *pointerToAge);
... the *
now means: "Give me the value of the variable my pointer is pointing to."
All of this is absolutely fundamental. You need to know it, and — more importantly — understand it. Honestly, don't even bother reading the rest of this chapter if you haven't grasped this yet. I'd rather be straight with you.
Feel free to re-read everything we just covered. I won't blame you if it didn't click right away — and hey, that's nothing to be ashamed of.
Just between us, it took me about a week to really wrap my head around this (okay, not full-time, let's be honest 😅). And to fully get the subtleties of pointers? Easily 2 or 3 months more (again, not full-time — don't panic).
So yeah, if you're feeling a bit lost, think about all those programming gurus out there: none of them mastered pointers on day one. And if someone did... well, I'd love to meet that person 😄
Sending a Pointer to a Function
The biggest advantage of pointers (though not the only one) is being able to pass them to functions so those functions can directly modify a variable in memory — rather than working on a copy like we saw earlier.
It's also super useful for performance, especially when you're working with variables that hold large chunks of data. It saves you from duplicating them every time.
So, how does this actually work? Well, there are a few ways to do it. Here's a first example:
void triplePointer(int *pointerToNumber);int main(int argc, char *argv[]) { int number = 5;
triplePointer(&number); // We send the address of 'number' to the function printf("%d", number); // We print 'number'. The function changed its value directly using the address
return 0; }
void triplePointer(int *pointerToNumber) { *pointerToNumber *= 3; // Multiply the value of the 'number' variable by 3 }
Result: 15
The triplePointer function takes a parameter of type int* (in other words, a pointer to an int). Here's what happens, step by step starting from main:
- A variable named number is created in main. It's assigned the value 5. All good so far.
- The function triplePointer is called, and we pass it the address of our number variable.
- The function receives this address in the pointerToNumber parameter. So inside triplePointer, we now have a pointer named pointerToNumber which holds the address of the number variable.
- Now that we have a pointer to number, we can modify the number variable directly in memory! All we have to do is write *pointerToNumber and voilà! For this example, we multiply it by 3.
- Back in the main function, the value of number is now 15 because triplePointer changed it directly in memory.
Of course, I could have just used a return, like we learned back in the chapter about functions. But the real value here is that with pointers, we can modify multiple variables in memory (so we can "return" more than one value). We're no longer limited to just one!
It really depends on your needs and your program. It's up to you. But just so you know, return is still widely used in C. Most often, it's used to return what we call an error code: the function returns 1 (true) if everything went fine, or 0 (false) if an error occurred.
Don't worry — we'll go over error handling in C a bit later on. 😉
Another Way to Pass a Pointer to a Function
In the code we just looked at, there wasn't actually a pointer declared in the main function — just the number variable. The only real pointer we used was inside the triplePointer function (with type int*).
You should know there's another way to write this exact same code, this time declaring a pointer in main:
void triplePointer(int *pointerToNumber);int main(int argc, char *argv[]) { int number = 5; int *pointer = &number; // 'pointer' takes the address of 'number'
triplePointer(pointer); // We send 'pointer' (i.e. the address of 'number') to the function printf("%d", *pointer); // Print the value of 'number' using *pointer
return 0; }
void triplePointer(int *pointerToNumber) { *pointerToNumber *= 3; // Multiply the value of 'number' by 3 }
To make it easier to compare, here's the code we used earlier, side by side. Pay close attention — there are subtle differences, and understanding them is key:
void triplePointer(int *pointerToNumber);int main(int argc, char *argv[]) { int number = 5;
triplePointer(&number); // Send the address of 'number' printf("%d", number); // Print the value directly
return 0; }
void triplePointer(int *pointerToNumber) { *pointerToNumber *= 3; }
In both cases, the result is the same: 15
What really matters is that the function receives the address of the number variable. Since the pointer holds this address, it works just the same! We're just doing it slightly differently — by creating a pointer in main.
And in the printf, I used *pointer to show the value of number (just for practice). I could have written number instead, and it would've worked the same, because both point to the same place in memory.
I remember it took me weeks to fully understand that these two bits of code actually do the same thing, just written in different ways. If you've managed to wrap your head around this, then honestly — congrats. Seriously. Well done. You've nailed exactly what I wanted to teach you about pointers. 🙌
To do that, it needs to be able to change the content of your variable. So it needs the variable's address:
int number = 0; scanf("%d", &number);
The function uses a pointer to number, and that's how it's able to modify it directly.
As we've just seen, we could also create a pointer and pass that to scanf:
int number = 0; int *pointer = &number; scanf("%d", pointer);
Back to Our Pokémon Problem
The chapter's almost over — it's time to return to our ongoing theme! :) If you've understood everything so far, you should now be able to solve the Pokémon problem on your own.
...
Well? What are you waiting for? Get to work, you lazy noobs!
Want to compare your solution with mine? Here it is! :D
#include <stdio.h> #include <stdlib.h>// Function to simulate the player's attack on a wild Pokémon void attackPokemon(int *pokeHP, char *pokeStatus) { // Deal damage *pokeHP -= 10; // Use the pointer to change the actual HP value printf("You dealt 10 damage to Bellsprout! Bellsprout's HP is now %d.\n", *pokeHP);
// Apply poison effect *pokeStatus = 'P'; // Use the pointer to change the actual status printf("Bellsprout is now poisoned! (%c)\n", *pokeStatus); }
int main() { int playerHP = 100; int pokeHP = 150; char playerStatus = 03; // ♥ means healthy (ASCII) char pokeStatus = 03; // ♥ means healthy (ASCII)
printf("Battle begins!\n"); printf("Bulbasaur HP: %d (%c), Bellsprout HP: %d (%c)\n\n", playerHP, playerStatus, pokeHP, pokeStatus);
// Player attacks the wild Pokémon attackPokemon(&pokeHP, &pokeStatus); // Pass addresses of pokeHP and pokeStatus
// Check post-attack state printf("\nAfter the attack:\nBulbasaur HP: %d (%c), Bellsprout HP: %d (%c)\n\n", playerHP, playerStatus, pokeHP, pokeStatus);
return 0; }
And the result:
Battle begins! Bulbasaur HP: 100 (♥), Bellsprout HP: 150 (♥)You dealt 10 damage to Bellsprout! Bellsprout's HP is now 140. Bellsprout is now poisoned! (P)
After the attack: Bulbasaur HP: 100 (♥), Bellsprout HP: 140 (P)
See, it works perfectly. In theory, everything I've explained so far should be enough — you shouldn't need anything new at this point.
But alright, for the sake of clarity — and because this is an important chapter — I'll go over it one more time. They say repetition helps things sink in... if that's true, good for you ;)
Here's what's going on:
- The variables pokeHP and pokeStatus are created in main (they're of type int and char, respectively).
- We send the addresses of pokeHP and pokeStatus to the attackPokemon function.
- attackPokemon receives those addresses in pointer variables also named pokeHP and pokeStatus. Keep in mind — names don't matter here. I could've named them h and s, or pointerPokeHP and pointerPokeStatus, it would work just the same.
- Because attackPokemon has pointers to the original variables, it can directly modify their values in memory. The only catch (and yeah, it's a bit annoying) is that you have to use an asterisk * in front of the pointer when you want to change the value it points to. If you forget to do that, you'll just be changing the address stored in the pointer — which is totally useless in this case :p
So yeah, if you find another way to solve it, don't get too excited. This was just a theoretical example to show you why pointers are useful. As we go further, that usefulness will become more and more obvious.
Oh, and by the way... Bulbasaur never actually learned the move "Poison Fang" ;) That was just to make a point.
Quick Recap Before We Wrap Up
Pointers do have a major flaw: they tend to scramble your brain. Believe me, I've been there. When I first tried to learn them, I kept mixing everything up. The * and the &. When to use which?
I can't magically fix that for you. You're going to have to read this chapter again and again until it all makes sense.
So here's a basic rule of thumb:
- * (Dereference): This means you're going into the address. You're asking, "what's the value at this address?"
- & (Address-of): This is the reverse — you're asking for the address of a given variable.
Don't freak out if it doesn't all click right away. That's completely normal — you now know it's just part of the learning curve ;)
Enjoyed this C / C++ course?
If you liked this lesson, you can find the book "Learn C Programing for Beginners" from the same authors, available on SiteRaw, in bookstores and in online libraries in either digital or paperback format. You will find a complete C / C++ workshop with many exclusive bonus chapters.