|
|
What Are Pointers?
Pointer Syntax
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:
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:
but this is the same as
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:
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:
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 2.1: 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 2.2: 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 2.3: 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:
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 2.4: 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.
  Help |
Feedback |
Make a request |
Report an error |
Send to a friend
|
|