Last time, we figure out how to create a pointer, but we left on the question of how to set a pointer’s value. As I mentioned, there are four ways to obtain a pointer:
- Since it’s an integer, code the address in manually.
- Get the address of a variable.
- Allocate some memory.
- Modify another pointer.
Let’s look at each of these in turn.
1. Since it’s an integer, code the address in manually.
Back in the old days, when C was brand new, this was a common technique. You knew that your program was the only program running, and you knew that your data was located at 0×400, so you can just stick 0×400 into the pointer, right?
Also, hardware in the pre-”plug ‘n’ play” days were located at fixed addresses in memory, which meant that you could communicate with (say) the mouse by reading from a fixed address. (Note: Memory-mapped IO is beyond the scope of this article, so look elsewhere for details)
These days, however, it’s not such a good idea. Heck, even under DOS, the potential for hardware address conflicts existed, necessitating the existence of those stupid DIP switches and config.sys. And, when multitasking systems became common, programs that existed on them needed to be aware that they weren’t necessarily located in the same spot they were used to. (Note: This is a gross and inaccurate simplification of how things work. Just wait until the virtual-memory post, where I’ll clear everything up — I promise!)
These days, fixed-address pointers are a thing of the past (on non-specialized devices); a footnote in the annals of IT history.
2. Get the address of a variable.
This is one of the more common uses. Every variable in your program has an address in memory, and so you can create a pointer that points to it:
int myVariable;
int * myPointer;
myPointer = &myVariable; |
Now, myPointer contains the address of myVariable (it “points” to it). If you then modify the pointer, it will show up in the variable, and vice versa:
*myPointer = 123; //we'll talk about this next time
printf("%d", myVariable); //prints 123 |
(I know we haven’t discussed how to actually USE a pointer, just assume I’m sticking the 123 into the place where myPointer points)
You can also pass the address of a variable to a function (that expects it), and the function can modify it too:
void myFunction(int * intptr) {
*intptr = 123;
}
int myVariable;
myFunction(&myVariable);
printf("%d", myVariable); //again, prints 123 |
As you can see, you don’t necessarily need a separate variable to store the pointer. In this case, since the thing you’re pointing at has a name (myVariable), you can ask for the address any time.
3. Allocate some memory.
This is trickier than working with local variables. Some times, at compile time, you don’t know how much memory you need. Maybe you’re working with a string that can be any length (a common source of bugs!). Perhaps you have a dynamically sized array. Either way, you can’t tell the compiler, and so it can’t manage the memory for you.
Fortunately, we have pointers.
#include
int * myArray = (int *) malloc(sizeof(int) * 100); |
Okay, this is a considerable step up from the previous example, so let’s look at it in detail.
You’ve seen this before, this is a pointer variable.
We will deal with this in a moment.
malloc (Memory ALLOCator) is the function that pulls a block of memory out of thin air for you. It’s not important (right now) to know where it gets it from, but only that it does.
malloc doesn’t know what you’re going to do with the memory, and so it can’t even begin to guess what type it should be. So, the return type is void* (that is, a pointer with no type). That’s why we need this bit:
This is a cast. It tells the compiler “The return value of this function is an integer pointer, even if it says otherwise”. Since void* and int* are not the same thing, you need to cast before the compiler will allow you to make the assignment.
NOTE: Technically, in regular C, you don’t need the cast; the compiler allows implicit casting from void* to any other pointer type. However, in C++, the cast IS required. Frankly, it’s better to just always cast — it doesn’t cost anything, so go nuts.
When we allocate memory, we need to tell malloc how much memory we want. In this case, we want enough room for 100 integers. Since we all know that an integer is four bytes, we can just write 400, right?
Well, no. Remember in part one, when I said that I was assuming 32-bit platforms, unless I say otherwise? I’m saying otherwise now. on a 64-bit platform, an integer can be up to eight bytes! So, if we allocate 400 bytes, we only have enough room for 50 ints. That’s bad, and is the root cause for a lot of bugs.
Fortunately, we can ask the compiler how big an integer is. That’s where the sizeof operator comes in (techincally, sizeof is an operator, not a function. It’s complicated, don’t ask.). Pass any type or variable, and it will be replaced with that type or variable’s size. The net result is that on 32-bit platforms, we’ll allocate 400 bytes, while on a 64-bit platform, we’ll allocate 800 bytes. Neato!
After all that, myArray now points to a block of memory 100 ints long, and we can treat it as anything else.
HOWEVER! There’s a lot more to using malloc and friends, which we’ll talk about in part five. Don’t even think about using malloc until you read it, because you’ll shoot yourself in the foot!
4. Modify another pointer.
In the last section, we learned how to allocate an array of arbitrary length. For completion’s sake, it’s also possible to allocate a static array of fixed length:
That’s a lot less complex, and does exactly what it seems to on both 32-bit and 64-bit platforms. The downside is that you need to know how big the array is at compile time. You can easily access the array using the [] notation:
printf("%d", myArray[0]); |
What does this have to do with pointers? Well, I’ll let you in on a secret. In both of these example, myArray has the same type: int*. Even though you never have to go anywhere near the asterisk key with static arrays, it’s actually a pointer.
Which brings us to the topic of modifying pointers. Recall that accessing a pointer looks like this:
The leading asterisk means “I am not talking about myArray, I’m talking about the thing myArray points at”. Now, consider this:
Wait, what did I just do? Technically, I didn’t do anything. Heck, the compiler will probably take the + 0 out by itself, and shake its head in bemusement. But, now, let’s change it once more:
What this say is “I am not talking about myArray, I’m talking about the thing after the thing myArray points at”.
Notice the similarity to arrays? Well, that’s because they’re the exact same thing. This:
Translates exactly to:
That’s what I mean by “modifying” the pointer. We’re modifying where the pointer points at, to access an array.
Just for clarification, the + 1 doesn’t mean “one byte later”. It means “one int later”. Think about how the array access translates into the pointer addition, and it should make sense. They are 100% equivalent.
The upshot is that it’s 100% legal to go the other way, and index an explicit pointer:
int * myPointer = ...; //points to whatever
myPointer[10] = 123;
Craaazy, huh?