7. Pointers--Cower In Fear!


Pointers are one of the most feared things in the C language. In fact, they are the one thing that makes this language challenging at all. But why?

Because they, quite honestly, can cause electric shocks to come up through the keyboard and physically weld your arms permantly in place, cursing you to a life at the keyboard.

Well, not really. But they can cause huge headaches if you don't know what you're doing when you try to mess with them.

7.1. Memory and Variables

Computer memory holds data of all kinds, right? It'll hold floats, ints, or whatever you have. To make memory easy to cope with, each byte of memory is identified by an integer. These integers increase sequentially as you move up through memory. You can think of it as a bunch of numbered boxes, where each box holds a byte of data. The number that represents each box is called its address.

Now, not all data types use just a byte. For instance, a long is often four bytes, but it really depends on the system. You can use the sizeof() operator to determine how many bytes of memory a certain type uses. (I know, sizeof() looks more like a function than an operator, but there we are.)

printf("a long uses %d bytes of memory\n", sizeof(long));

When you have a data type that uses more than a byte of memory, the bytes that make up the data are always adjacent to one another in memory. Sometimes they're in order, and sometimes they're not, but that's platform-dependent, and often taken care of for you without you needing to worry about pesky byte orderings.

So anyway, if we can get on with it and get a drum roll and some forboding music playing for the definition of a pointer, a pointer is the address of some data in memory. Imagine the classical score from 2001: A Space Odessey at this point. Ba bum ba bum ba bum BAAAAH!

Ok, so maybe a bit overwrought here, yes? There's not a lot of mystery about pointers. They are the address of data. Just like an int can be 12, a pointer can be the address of data.

Often, we like to make a pointer to some data that we have stored in a variable, as opposed to any old random data out in memory whereever. Having a pointer to a variable is often more useful.

So if we have an int, say, and we want a pointer to it, what we want is some way to get the address of that int, right? After all, the pointer is just the address of the data. What operator do you suppose we'd use to find the address of the int?

Well, by a shocking suprise that must come as something of a shock to you, gentle reader, we use the address-of operator (which happens to be an ampersand: "&") to find the address of the data. Ampersand.

So for a quick example, we'll introduce a new format specifier for printf() so you can print a pointer. You know already how %d prints a decimal integer, yes? Well, %p prints a pointer. Now, this pointer is going to look like a garbage number (and it might be printed in hexidecimal instead of decimal), but it is merely the number of the box the data is stored in. (Or the number of the box that the first byte of data is stored in, if the data is multi-byte.) In virtually all circumstances, including this one, the actual value of the number printed is unimportant to you, and I show it here only for demonstration of the address-of operator.

#include <stdio.h>

int main(void)
{
    int i = 10;

    printf("The value of i is %d, and its address is %p\n", i, &i);

    return 0;
}

On my laptop, this prints:

The value of i is 10, and its address is 0xbffff964

(I can show off a bit here, and say that from experience the address to me looks like its on the stack...and it is, since it's a local variable, and all locals go on the stack. Am I cool or what. Don't answer that.)

7.2. Pointer Types

Well, this is all well and good. You can now successfully take the address of a variable and print it on the screen. There's a little something for the ol' resume, right? Here's where you grab me by the scruff of the neck and ask politely what the frick pointers are good for.

Excellent question, and we'll get to that right after these messages from our sponsor.

ACME ROBOTIC HOUSING UNIT CLEANING SERVICES. YOUR HOMESTEAD WILL BE DRAMATICALLY IMPROVED OR YOU WILL BE TERMINATED. MESSAGE ENDS.

Welcome back to another installment of Beej's Guide to Whatever. When we met last we were talking about how to make use of pointers. Well, what we're going to do is store a pointer off in a variable so that we can use it later. You can identify the pointer type because there's an asterisk (*) before the variable name and after its type:

int main(void)
{
    int i;  /* i's type is "int" */
    int *p; /* p's type is "pointer to an int", or "int-pointer" */

    return 0;
}

Hey, so we have here a variable that is a pointer itself, and it can point to other ints. We know it points to ints, since it's of type int* (read "int-pointer").

When you do an assignment into a pointer variable, the type of the right hand side of the assignment has to be the same type as the pointer variable. Fortunately for us, when you take the address-of a variable, the resultant type is a pointer to that variable type, so assignments like the following are perfect:

int i;
int *p; /* p is a pointer, but is uninitialized and points to garbage */

p = &i; /* p now "points to" i */

Get it? I know is still doesn't quite make much sense since you haven't seen an actual use for the pointer variable, but we're taking small steps here so that no one gets lost. So now, let's introduce you to the anti-address-of, operator. It's kind of like what address-of would be like in Bizarro World.

7.3. Dereferencing

A pointer, also known as an address, is sometimes also called a reference. How in the name of all that is holy can there be so many terms for exactly the same thing? I don't know the answer to that one, but these things are all equivalent, and can be used interchangably.

The only reason I'm telling you this is so that the name of this operator will make any sense to you whatsoever. When you have a pointer to a variable (AKA "a reference to a variable"), you can use the original variable through the pointer by dereferencing the pointer. (You can think of this as "de-pointering" the pointer, but no one ever says "de-pointering".)

What do I mean by "get access to the original variable"? Well, if you have a variable called i, and you have a pointer to i called p, you can use the dereferenced pointer p exactly as if it were the original variable i!

You almost have enough knowledge to handle an example. The last tidbit you need to know is actually this: what is the dereference operator? It is the asterisk, again: *. Now, don't get this confused with the asterisk you used in the pointer declaration, earlier. They are the same character, but they have different meanings in different contexts.

Here's a full-blown example:

#include <stdio.h>

int main(void)
{
    int i;
    int *p;  // this is NOT a dereference--this is a type "int*"

    p = &i; // p now points to i

    i = 10;  // i is now 10
    *p = 20; // i (yes i!) is now 20!!

    printf("i is %d\n", i);   // prints "20"
    printf("i is %d\n", *p);  // "20"!  dereference-p is the same as i!

    return 0;
}

Remember that p holds the address of i, as you can see where we did the assignment to p. What the dereference operator does is tells the computer to use the variable the pointer points to instead of using the pointer itself. In this way, we have turned *p into an alias of sorts for i.

7.4. Passing Pointers as Parameters

Right about now, you're thinking that you have an awful lot of knowledge about pointers, but absolutely zero application, right? I mean, what use is *p if you could just simply say i instead?

Well, my feathered friend, the real power of pointers comes into play when you start passing them to functions. Why is this a big deal? You might recall from before that you could pass all kinds of parameters to functions and they'd be dutifully copied onto the stack, and then you could manipulate local copies of those variables from within the function, and then you could return a single value.

What if you wanted to bring back more than one single piece of data from the function? What if I answered that question with another question, like this:

What happens when you pass a pointer as a parameter to a function? Does a copy of the pointer get put on the stack? You bet your sweet peas it does. Remember how earlier I rambled on and on about how EVERY SINGLE PARAMETER gets copied onto the stack and the function uses a copy of the parameter? Well, the same is true here. The function will get a copy of the pointer.

But, and this is the clever part: we will have set up the pointer in advance to point at a variable...and then the function can dereference its copy of the pointer to get back to the original variable! The function can't see the variable itself, but it can certainly dereference a pointer to that variable! Example!

#include <stdio.h>

void increment(int *p) /* note that it accepts a pointer to an int */
{
    *p = *p + 1; /* add one to p */
}

int main(void)
{
    int i = 10;

    printf("i is %d\n", i); /* prints "10" */

    increment(&i); /* note the address-of; turns it into a pointer */

    printf("i is %d\n", i); /* prints "11"! */

    return 0;
}

Ok! There are a couple things to see here...not the least of which is that the increment() function takes an int* as a parameter. We pass it an int* in the call by changing the int variable i to an int* using the address-of operator. (Remember, a pointer is an address, so we make pointers out of variables by running them through the address-of operator.)

The increment() function gets a copy of the pointer on the stack. Both the original pointer &i (in main()) and the copy of the pointer p (in increment()) point to the same address. So dereferencing either will allow you to modify the original variable i! The function can modify a variable in another scope! Rock on!

Pointer enthusiasts will recall from early on in the guide, we used a function to read from the keyboard, scanf()...and, although you might not have recognized it at the time, we used the address-of to pass a pointer to a value to scanf(). We had to pass a pointer, see, because scanf() reads from the keyboard and stores the result in a variable. The only way it can see that variable that is local to that calling function is if we pass a pointer to that variable:

int i = 0;

scanf("%d", &i);        /* pretend you typed "12" */
printf("i is %d\n", i); /* prints "i is 12" */

See, scanf() dereferences the pointer we pass it in order to modify the variable it points to. And now you know why you have to put that pesky ampersand in there!