Introduction to pointers
Whenever you run a program, it is first
loaded (typically from disk) into the computer’s memory. Thus, all
elements of your program are located somewhere in memory.
Memory is typically laid out as a sequential series of
memory locations; we usually refer to these locations as eight-bit
bytes but actually the size of each space depends
on the architecture of the particular machine and is usually called that
machine’s word size.
Each space can be uniquely distinguished from all other spaces by its
address. For the purposes of this discussion,
we’ll just say that all machines use bytes that have sequential addresses
starting at zero and going up to however much memory you have in your
computer.
Since your program lives in memory while
it’s being run, every element of your program has an address. Suppose we
start with a simple program:
//: C03:YourPets1.cpp
#include <iostream>
using namespace std;
int dog, cat, bird, fish;
void f(int pet) {
cout << "pet id number: " << pet << endl;
}
int main() {
int i, j, k;
} ///:~
Each of the elements in this program has
a location in storage when the program is running. Even the function occupies
storage. As you’ll see, it turns out that what an element is and the way
you define it usually determines the area of memory where that element is
placed.
There is an operator in C and C++ that
will tell you the address of an element. This is the
‘&’ operator. All you do is precede the identifier name
with ‘&’ and it will produce the address of that
identifier. YourPets1.cpp can be modified to print out the addresses of
all its elements, like this:
//: C03:YourPets2.cpp
#include <iostream>
using namespace std;
int dog, cat, bird, fish;
void f(int pet) {
cout << "pet id number: " << pet << endl;
}
int main() {
int i, j, k;
cout << "f(): " << (long)&f << endl;
cout << "dog: " << (long)&dog << endl;
cout << "cat: " << (long)&cat << endl;
cout << "bird: " << (long)&bird << endl;
cout << "fish: " << (long)&fish << endl;
cout << "i: " << (long)&i << endl;
cout << "j: " << (long)&j << endl;
cout << "k: " << (long)&k << endl;
} ///:~
The (long) is a
cast. It says
“Don’t treat this as if it’s normal type, instead treat it as
a long.” The cast isn’t essential, but if it wasn’t
there, the addresses would have been printed out in hexadecimal instead, so
casting to a long makes things a little more
readable.
The results of this program will vary
depending on your computer, OS, and all sorts of other factors, but it will
always give you some interesting insights. For a single run on my computer, the
results looked like this:
f(): 4198736
dog: 4323632
cat: 4323636
bird: 4323640
fish: 4323644
i: 6684160
j: 6684156
k: 6684152
You can see how the variables that are
defined inside main( ) are in a different area than the variables
defined outside of main( ); you’ll understand why as you learn
more about the language. Also, f( ) appears to be in its own area;
code is typically separated from data in memory.
Another interesting thing to note is that
variables defined one right after the other appear to be placed contiguously in
memory. They are separated by the number of bytes that are required by their
data type. Here, the only data type used is int, and cat is four
bytes away from dog, bird is four bytes away from cat, etc.
So it would appear that, on this machine, an int is four bytes
long.
Other than this interesting experiment
showing how memory is mapped out, what can you do with an address? The most
important thing you can do is store it inside another variable for later use. C
and C++ have a special type of variable that holds an address. This variable is
called a pointer.
The
operator that defines a pointer is the same as the one used for multiplication:
‘*’. The compiler knows that it isn’t multiplication
because of the context in which it is used, as you will see.
When you define a pointer, you must
specify the type of variable it points to. You start out by giving the type
name, then instead of immediately giving an identifier for the variable, you say
“Wait, it’s a pointer” by inserting a star between the type
and the identifier. So a pointer to an int looks like
this:
int* ip; // ip points to an int variable
The association of the
‘*’ with the type looks sensible and reads easily, but it can
actually be a bit deceiving. Your inclination might be to say
“intpointer” as if it is a single discrete type. However, with an
int or other basic data type, it’s possible to
say:
int a, b, c;
whereas with a pointer, you’d
like to say:
int* ipa, ipb, ipc;
C syntax (and by inheritance, C++ syntax)
does not allow such sensible expressions. In the definitions above, only
ipa is a pointer, but ipb and ipc are ordinary ints
(you can say that “* binds more tightly to the identifier”).
Consequently, the best results can be achieved by using only one definition per
line; you still get the sensible syntax without the confusion:
int* ipa;
int* ipb;
int* ipc;
Since a general guideline for C++
programming is that you should always initialize a variable at the point of
definition, this form actually works better. For example, the variables above
are not initialized to any particular value; they hold garbage. It’s much
better to say something like:
int a = 47;
int* ipa = &a;
Now both a and ipa have
been initialized, and ipa holds the address of a.
Once you have an initialized pointer, the
most basic thing you can do with it is to use it to modify the value it points
to. To access a variable through a pointer, you
dereference the pointer using the same operator
that you used to define it, like this:
*ipa = 100;
Now a contains the value 100
instead of 47.
These are the basics of pointers: you can
hold an address, and you can use that address to modify the original variable.
But the question still remains: why do you want to modify one variable using
another variable as a proxy?
For this introductory view of pointers,
we can put the answer into two broad categories:
- To change “outside
objects” from within a function. This is perhaps the most basic use of
pointers, and it will be examined
here.
- To achieve
many other clever programming techniques, which you’ll learn about in
portions of the rest of the