Skip over navigation

What Are Pointers?

Pointer Syntax

Problems

Problems

So you want a pointer, huh?

Pointer syntax, though relatively straightforward, can be confusing at first.

Before we can use a pointer, the first thing we need is a pointer itself, so how do we declare one? Declaration of a pointer is done just like any other variable:


int *steve;

If you look at the declaration above, you'll notice that it looks the same as a declaration of an int, with the exception of the asterisk (*) in front of steve. The asterisk is used in a variable declaration to tell the computer we'd like a pointer. In the above case, we're asking the computer for a pointer variable, named steve that can point to a integer. To compare: int steve -> steve is an integer variable int *steve -> steve is a pointer variable that can point to an integer variable

Let's look at some more:

Declaration What it means
int steve steve is an integer
int *steve steve is a pointer to an integer
char steve steve is a character
char *steve steve is a pointer to a character
long steve steve is a long integer
long *steve steve is a pointer to a long integer
unsigned char steve steve is an unsigned character
unsigned char *steve steve is a pointer to an unsigned character

But pointers can point to more than just the simple data types like integers and characters. We can have pointers to numerous instances of a data type. In fact, this is so common that it is given a separate name (an array) and a separate syntax. See the Arrays SparkNote for details on the use of arrays.

In addition, we can declare pointers to data types that we define ourselves:


typedef struct _person_t {
	char name[100];
	int age;
} person_t;

person_t *steve;
Here, steve is a pointer to a variable of type person_t.

We can take this one step further. Not only can we have pointers to simple data types like integers and characters, and more complex data types defined with structs, but we can actually have pointers to other pointers. How do we do that? Recall that the asterisk before the variable name in the declaration means that this variable is a pointer to the specified type. To make this easier to visualize, let's change (insignificantly) where we place the star. If we want to declare a pointer to an integer, we can do it as:


int *steve
but this is the same as

int* steve
The computer doesn't care where we place the *. int *steve is the same as int * steve, which is the same as int* steve.

We can think of the variable steve as having the type int*, in other words, its type is a "pointer to an integer". So if we have the data type int*, how might we declare a pointer to this data type? Just like any other:


int* *steve
steve here is a pointer to a pointer to a integer. The variable steve is now capable of holding the address of a pointer variable, and that pointer variable in turn can hold the address of an integer. Normally, the declaration above would be written as int** steve or int **steve.

We don't have to stop here. We can have pointers to pointers to pointers to pointers, ad infinitum. Go ahead and try it. Start up your favorite C/C++ compiler and try typing in:


int ***steve

The compiler should have no problem understanding that steve is a pointer to a pointer to a pointer to a pointer to a pointer to a pointer to a pointer to an integer. The likelihood that you would need to do this is small, but the occasion might arise.

There is one thing to be cautious about when declaring pointers. The asterisk operator only applies to one variable being declared. For example, in the following code


int* steve, toub, sparknote;
we've actually only declared one pointer, steve. Both toub and sparknote are actually integers, not pointers to integers. To the computer, this actually looks something like:

int (*steve), toub, sparknote
If we want toub and sparknote to be pointers as well, we need to state this explicitly, as in:

int *steve, *toub, *sparknote
For this reason, many programmers prefer placing the asterisk next to the variable to minimize confusion. Placing the asterist next to the type also has its merits. In the end it comes down to a matter of personal style and taste.

I have a pointer... what does it contain?

Notice that we've been careful to say that our pointers can point to a variable of a certain type. The implication is that they don't necessarily point to a variable of that type. In fact when you first declare a pointer, it really can't be used. Why? Because it doesn't point to anything (more accurately it points to a random location in memory, which is extremely unlikely to be usable). A pointer's job in life is to point to another variable by storing its address. How do we get the address of another variable? The answer is the & operator, commonly referred to as the "address-of" operator.

The & operator takes a variable and gives back its address. So for example, if we have a variable int steve, the expression &steve is the address of steve. This address can be stored in a pointer.


int steve;
int *ptr;
steve = 5;
ptr = &steve;
In the above code, we declare two variables, steve, an integer, and ptr, a ptr to an integer. steve is then given the integer value 5. On the next line, ptr = &steve tells the computer, "take the address of the variable steve and store that address into the variable ptr". Pretty straightforward, right?

Figure %: Pointer ptr points to integer steve

The address stored in the pointer is like any other value in a variable. We can assign it to another variable:


int steve;
int *ptr;
int *ptr2;
steve = 5;
ptr = &steve;
ptr2 = ptr;
Figure %: Copying an address to another pointer
We can check to see if two pointers contain the same address:

int steve;
int *ptr;
int *ptr2;
steve = 5;
ptr = &steve;
ptr2 = &steve;
if (ptr == ptr2) printf("Equal\n");
We can even perform simple arithmetic operations on them, like subtraction. More on that in a later section.

Using the information in a pointer

Now that we have the ability to create pointers and to put addresses into them, what can we do with them? Remember that a pointer's job in life is to hold the address of a location in memory. Wouldn't it be great if we could then take that address and find out what it contains? Well we can; that's the whole idea.

To find out what the memory at an address holds, we use the * operator. As we've seen, the * operator has multiple meanings in C. It can be the multiplication operator. It can be used to declare a pointer. It can also be used to dereference a pointer.

Dereference? Yes. To dereference a pointer means to take the address contained in the pointer variable, and go find whatever data resides at that address. You might find it helpful to think of an analogy. Think of the phone book as a huge array of pointers. Each entry in the phonebook contains the address of a person's house. To find out who lives in that house, you get in your car, drive over there, knock on the door, and see who answers. That process of driving to the persons house and seeing who was inside is like dereferencing a pointer.

To dereference a pointer, we use the asterisk. By putting the asterisk operator in front of a pointer, we're telling the computer to go fetch the memory addressed by that pointer.


int steve;
int *ptr;
steve = 5;
ptr = &steve;
printf("%d\n", *ptr);
In the above code, we again declare two variables, an integer and a pointer to an integer, then store the value 5 into steve and the address of steve into ptr. The pointer ptr now points to the variable steve. Therefore, *ptr is equivalent to the variable steve and can be used synonymously. If we ask for the value of *ptr, we're going to get whatever value steve holds. If we store something into *ptr, we're storing that something into steve.

Figure %: Dereferencing a pointer

Something to be very careful of here. When you first declare a pointer, as mentioned above, it doesn't point to anything meaningful; like all variables when they are first declared, it contains garbage. When you declare a variable, the computer goes and sees what memory it has available and then assigns your program a small chunk of it for the variable. However, it doesn't clear out the memory at that location. Whatever was in that memory location before you were given the right to it is still there. This can lead to trouble if you're not careful.

Look at the following code:


int *ptr;
*ptr = 5;
What did we just tell the computer to do? We declared a pointer variable, and then we immediately dereferenced it and stored the value 5. Do you see the problem here? We haven't initialized ptr, meaning that whatever it contained before we were given it is still there; in other words, it points to a random place in memory. We then tell the computer, "go to this random location in memory and try to store the value 5 there". Hopefully your computer's operating system is much smarter than this. That random place in memory could be anywhere: it could be memory being used by Microsoft Word, it could be memory being used by the operating system, it could be memory being used by the countdown timer for the nuclear warhead sitting in your backyard. The point is, you don't want to go modifying memory that doesn't belong to your program, and neither does your operating system. If you try to do something like this and the operating system sees that what you're attempting to do may be harmful to itself or other programs, it will stop you the only way it can, by killing your program. This is commonly referred to as crashing, or causing a segmentation fault. The operating system attempts to protect the rest of the system by shutting down your program if your program behaves in an unacceptable way.

Figure %: An uninitialized pointer initially points to a random location

Now that we've seen the dereferencing operator, the declaration of pointers might make a little more sense. Instead of thinking of int *ptr as a pointer to an integer, we can imagine that "*ptr is an integer". Of course, this method of thinking about pointers has some drawbacks, mostly linked with the memory problem described above. When you first declare int *ptr, it most likely doesn't point to anything valid, but if you think of int *ptr as declaring *ptr as an integer, you might think you can use it just like any other integer. Unfortunately you can't because, again, it most likely doesn't point to anything legal.

Pointers to structures: the -> operator

Let's say we have the following code:


typedef struct _person_t {
	char name[100];
	int age;
} person_t;

person_t steve;
person_t *ptr = &steve;
We've created a data type called person_t that holds a name and an age, we've created a variable of that type, steve, and we've created a pointer variable ptr that can point to a person_t. As described above, we can dereference ptr just like any other pointer variable by placing an asterisk in front of the name, as in *ptr. *ptr can be used just like steve.

Remember that with structures (and classes in C++) we use the . operator to get at the fields contained within the complex type. So for example, to access the steve variable's age field we would write steve.age. We can do the same thing with the *ptr, for example (*ptr).age. Why, you might be asking, do I have those parentheses there? The answer is that the . operator binds tighter than the * operator, meaning that this is equivalent to *(ptr.age). The computer will first try to get the age field of the ptr variable, and then attempt to dereference it. As such, we need to put in the parenthesis ourselves to force the computer to do what we want.

This can get tedious, especially considering the frequency with which pointers and structures are used in combination. To make our lives easier, C provides another operator, the -> operator, which can be thought of as the "dereference this pointer and get a specific field" operator. Instead of writing (*ptr).age, we can just write ptr->age.

Pointing to... NOTHING

Often it is useful to have special values to indicate that a variable is not valid. For pointers, C/C++ gives us a special value to use, the NULL value, that says just that. Programmers use the value NULL to signify that a pointer does not contain a valid address, that it doesn't point to anything useful. A particularly useful feature of NULL is that it's guaranteed to be interpreted as "false" in if constructs:


int* steve = NULL;
int x;

...

/* Only dereference steve it it's not NULL. */
if(steve) {
	x = *steve;
}

All the basic tools

You now possess all of the basic basic knowledge needed to use pointers. However, why we'd want to use pointers may still seem a mystery. As you progress through the following sections of this guide, you'll see how useful pointers really are.

Follow Us