Data Types and Variables
A quick review of one of the very first things we learned about… variables!
Variables hold VALUES. Values are just bits that we interpret a certain way. Recall that a program has data associated with it (variables, instructions, etc). That data needs to be stored in memory (RAM) when the program is running. This means that the variables of a program exist at some LOCATION in memory. For example:
//This is somewhere in memory, and holds a value
int playerHealth = 50;
//Same here!
float playerSpeed = 7.5f
//And same with this array!
char myLetters[26];However, programmers do NOT get to choose the memory location of a variable. As you can see above, the only thing the programmer gets to decide is the name/value of a variable. And that’s OK! Having to choose WHERE our variables live in memory would be an incredibly messy and complicated business that we want no part of!
The only thing we can say about the variables above, in terms of their memory, is that they exist SOMEWHERE in our program’s memory. The only guarantee about memory location is that we know all of the elements in the character array exist next to each other, sequentially, in memory. But otherwise we have no knowledge or guarantee about where playerHealth, playerSpeed, or myLetters are located in relation to each other at all.
Memory Addresses
So, what do memory locations look like? We call a location in memory a memory address. We actually have an operator that can tell us the memory address of a variable. We’ve used it before:
//Get user input to store into our in variable, x
int x = 0;
scanf("%d", &x);What if we wanted to know what the memory address of x was? Just for fun? The easiest way to find out is to just print out the memory address! There’s a printf format specifier for that:
int x = 0;
printf("%p", &x);When this code runs you’ll likely see a very large number. And it’s a memory address. Cool?
What if we wanted to have variables that could store a memory address as a value? Similar to how we store integers, floats, etc as values? Well we can totally do that:
int* myFirstPointer;Wait, what the heck? What’s going on here? What is this new sorcery? We’re defining a new variable, called myFirstPointer. This variable is NOT an integer type. Instead, it is a new type: int pointer.
| integer type | integer pointer type |
|---|---|
int x; | int* myPointer; |
OK, what’s an int pointer? It’s a variable whose value is a memory address! And not just any address… the address of where an integer is located in memory! So, we’ve had the peanut butter all along:
//Address operator, used on int x. The value of this expression is
//a memory address, but we don't use it, it's just discarded
//because we're not storing it off anywhere
int x;
&x;Now we’ve got the jelly:
//This variable stores a memory address to an int
int* myFirstPointer;Let’s smoosh together this PBJ!
/*Line 1*/ int x;
/*Line 2*/ int* myFirstPointer;
/*Line 3*/ myFirstPointer = &x;QUESTION
What is the value of
myFirstPointeron line 2, when it is created? (Hint: you should be able to answer this without any knowledge of what pointers are!)
ANSWER
Undefined! We didn’t assign any value to it, so it’s likely whatever garbo was lying around in memory.
QUESTION
What is the value of
myFirstPointeron line 3? (Hint: let’s assume, for fun, that our integer x is located at the memory address 200) (Hint: I am giving you a super easy T-ball question, it isn’t a trick, the answer is above this text, oh look there it goes, goodbye answer!)
ANSWER
The value of
myFirstPointeris 200
Crazy sauce! We know where our integer variable, x, is located in memory. We are storing that location (memory address) as a value in myFirstPointer. Remember, there’s nothing SPECIAL about this yet. myFirstPointer is just another variable, storing another value. It just so happens that the value we’re storing in myFirstPointer is a memory address, which is just a number.
Let’s go over that slowly, again, to make sure we’ve got it.
An int pointer is a variable just like any other, and the bits inside are interpreted as a memory address. (so the value isn’t an integer, a bool, a float, or anything else… it is a memory address)
Multiple pointer variables can point at the same memory location (meaning, the VALUE of those pointer variables can be the same).
Let’s say it a different way:
Two different integer variables, x and y, can both have the value 5. Two different integer pointer variables, pX and pY, can both have the value 0x00000008.
The Indirection Operator
Alright, so WHY is any of this interesting? So we can store memory addresses in variables, that doesn’t seem very useful. Well, pointers are actually incredibly powerful, due to something called the indirection operator.
Before we introduce the indirection operator, let’s take it slow:
int x = 5;
int* pX = &x;We’ve got an integer, x, and an integer pointer, pX. We say that pX POINTS at x (meaning, the value of pX is the memory location of the variable x). Cool, same stuff we’ve done before.
So let’s introduce our new operator below:
*pX = 10;WHAT THE HECK IS GOING ON!?!?!
The line of code above is using the indirection operator (also called dereference operator). This operator allows us to read/write the value of x, using pX. That last part might sound confusing, so let’s say it again, one more time:
//int x, setting its value to 5
int x = 5;
//int pointer, pX, whose value is the address of x
//we say that pX is POINTING at x
int* pX = &x;
//we are using the indirection operator to treat pX as if it
//were x, so we can read/write the data at x's memory location
*pX = 10;So after that last line of code above runs, the value of x will be 10! Isn’t that wild? We’ve changed the value of x without ever using the actual variable x!
Another way to think about the line of code above:
//THESE TWO LINES ARE BASICALLY THE SAME THING
*pX = 10;
x = 10;
pX ------> Pointer Variable
*pX -----> Dereferenced Pointer VariableWhenever we use the indirection operator, we can think of it as temporarily changing the pointer variable into the variable it POINTS to; in the code above, we’re treating (*pX) as if it is x.
A quick warning: don’t get confused by the 3 different meanings behind the asterisk (*) in C!
- Multiplication
- Declaring a pointer variable
- Dereferencing a pointer (indirection operator)
//Multiplication
int x = 5 * 87;
int y = x * 4;
x = y * y * 22;
x = y;
//Declaring a pointer variable
int* a = &x;
int* b = &y;
a = b;
//The indirection operator
*a = 10; //Sets the value of x to 10
*b = 555; //Sets the value of y to 555
//No those are NOT typos, yes I do really mean
//the values of x and y, not a and b, that
//is how the indirection operator worksUses of the Indirection Operator
We can use pointers to change the value of variables inside of completely separate functions! For example, look at the function below:
//Remember, this doesn't accomplish anything, and is total garbage
void BadSwap(int a, int b);
{
int swap = a;
a = b;
b = swap;
}If we called this function like so:
int main()
{
int x = 5;
int y = 10;
BadSwap(x, y);
}The values of x and y would NOT CHANGE AT ALL. That’s because we’re passing the values of 5 and 10 over to the BadSwap function, and then swapping the a and b variables in THAT function. But that will NOT affect x and y in our main function. Also, don’t make the mistake of thinking this is because the BadSwap function has a return type of void; that doesn’t have anything to do with what we’re talking about!
OK, so what if we wanted to write a function that DID swap the values of two integer variables? We could do so by passing the locations of the variables instead, and then altering the values at those locations in memory via the dereference operator. For example:
//Swaps values of whatever integers a and b point at, for real
void GoodSwap(int* a, int* b);
{
int swap = *a;
*a = *b;
*b = swap;
}But of course, we would need to change how we call the function from main:
int main()
{
int x = 5;
int y = 10;
GoodSwap(&x, &y);
}Since GoodSwap takes two integer pointers (NOT integers), when calling the GoodSwap function we must pass the addresses of where two integers are located in memory. Hence, the address-of operator &.
This function will now actually swap the values of x and y! After the code above runs, x will be 10 and y will be 5.