Stacks are fun once you master them. But now we will be discussing about memory management at its simplest... pointers. If you are taking CS140 or CS302 sometime soon, you're required to use pointers in those classes (They kind of force it in their lab assignments).
So let's think in C++ here, since you'll be using that for a while. Let's declare an array of size 4.
Code (C++)
#include <iostream>
using namespace std;
const int ARR_SIZE = 4;
int main() {
int arr[ARR_SIZE]; //Make an array size 4.
arr[0] = 1;
arr[1] = 9;
arr[2] = 27;
arr[3] = 42;
}
Now this code should be trivial at this point. Here's a (poorly made) table of what's going on:
arr[0] |
arr[1] |
arr[2] |
arr[3] |
1 |
9 |
27 |
42 |
Makes sense right (This is from CS102)? Great! Now let's see "how" it works, since that is what is important here.
When you declare a variable, you are telling the computer to assign that variable somewhere in memory.
Therefore, each variable has some address assigned to it. You can get this address by using the "address-of" operator (&).
For example:
Code (C++)
int a = 0;
cout << &a; //Print out the address of "a".
Now, the output of this will be different pretty much every time you run the application.
The reason why is because the Operating System gives a random address every time.
Your program basically asks the Operating System (really nicely) for memory to allocate the variable.
The Operating System, then, is like "Hmm... ok this address looks good, here you go".
It should be something like this:
"...Your point?"
Right, right... so here's where it relates. Let's look at that table again... but let's also look at some example addresses.
|
arr[0] |
arr[1] |
arr[2] |
arr[3] |
Value |
1 |
9 |
27 |
42 |
Address |
0x1000 |
0x1004 |
0x1008 |
0x100C |
When we defined
int arr[ARR_SIZE];, all memory in this array is guaranteed to be right next to each other. It's guaranteed to be "contiguous". So because these are integers, they take up 4 bytes. So
arr[1] is 4 bytes ahead of
arr[0].
Because of this, we can grab a pointer to the first element of the array, and then access the others easily. So let's use a pointer for the first time ever.
Code (C++)
#include <iostream>
using namespace std;
const int ARR_SIZE = 4;
int main() {
int arr[ARR_SIZE]; //Make an array size 4.
arr[0] = 1;
arr[1] = 9;
arr[2] = 27;
arr[3] = 42;
int* p = &arr[0]; //Access arr[0], and grab the address that points to it.
cout << p << endl; //Print out the address
cout << *p << endl; //Dereference the value at address "p"... which is "arr[0]". Print.
}
So the code will, as the comments say, grab the address pointing toward
arr[0], print out that address... and then dereference that pointer to get the value at that location.
The value at that location is arr[0]'s value.
Then it will print out that value.
Here's the two operators, in case you are confused:
- & (Address-of Operator) - This will grab the address that holds a value.
- * (Dereference Operator) - This will take an address, go to it, and grab the value at that address.
What gets confusing is that "*" can also mark a variable as a pointer (hence
int *a;)... which holds an address.
But yet it is an operator that involves using the address to get a value. Blame the C++ designers for that.
Now for the magic
Let's get
arr[1] with pointer arithmetic.
Code (C++)
#include <iostream>
using namespace std;
const int ARR_SIZE = 4;
int main() {
int arr[ARR_SIZE]; //Make an array size 4.
arr[0] = 1;
arr[1] = 9;
arr[2] = 27;
arr[3] = 42;
int* p = &arr[0]; //Access arr[0], and grab the address that points to it.
p++; //Increment p by sizeof(int)
cout << p << endl; //Print out the address
cout << *p << endl; //Dereference the value at address "p"... which is "arr[0]". Print.
}
Stop the presses! The only change here is that
p++.
But what does it do?
You're used to it in for loops where it increments the value by 1.
You're correct there.
However, the rules of the game changes here.
The pointer will increment by the size of the data type the pointer represents.
Since the type of the pointer is an
int, it will increment by the size of an int, which is
usually 4 bytes.
As expected, it prints out "9".
Output
9