Pointers

Pointers are a mechanism to refer to objects in memory indirectly. They are often used with arrays, which are collections of same size objects.

A pointer is the location of something. It is not the thing itself. It tells you where the thing is. Think of the address on an envelope. You are not really interested in the address, but the building it represents. By putting a different address on the envelope, you send it to a different building. We talked about how variables are a chunk of memory and have a name, content, location and type. This is also true of pointers.

They are variables. While they have different types, all pointers are the same size. They are big enough to hold a machine address. This is usually 32 bits. The content of a pointer is the location of another variable. It is the memory address where the variable starts in memory.

Perhaps a better analogy than house addresses is phone numbers. A phone number is assigned to you. If you move, many times you keep the same phone number, even though it connects to a different phone. The phone switch knows that when it receives that number it should now ring the phone in the new house instead of the old house.

Pointers are often used the same way. A single variable in your program can point at different times to different other variables. You can make a function that uses a piece of data and give it a different datum each time.

Syntax

The type of a pointer is 'pointer to something', where 'something' is some other type. So, you can have 'pointer to float' and 'pointer to int' and these are different types, just as float and int are different. To make a pointer variable, you put a * before its name.

int * ipointer;  // pointer to integer
float * fpointer;  // pointer to float

By themselves, these aren't much use. Above, I said the contents of a pointer was the address of something else. So, given these other definitions:

int itarget;
float ftarget;

We can point the pointers at them;

ipointer = &itarget;
fpointer = &ftarget;
The unary ampersand operator ( not to be confused with the binary one) retrieves the location of the variable that follows it. After we run the example program, we get this output,
 
contents of ipointer is 0x0066FDF0
contents of fpointer is 0x0066FDE4
contents of cpointer is c¦¦¦_²f  // this is different because char pointers are special in C++
The size of ipointer is 4
The size of itarget is 4
The size of fpointer is 4
The size of ftarget is 8
The size of cpointer is 4
The size of ctarget is 1

Note that, while all the pointers are the same size, the things they point at are different size.

Now that I have a pointer, what do I do with it? I am rarely interested in its contents directly as they are memory addresses. Usually, I want the contents of the thing it points to. The process of getting at the thing pointed to is called dereferencing. To do this in C++, we use the unary * operator.

 cout << "The value in itarget is " << itarget << "  using the pointer it is " << *ipointer << endl;

If we add these lines to the example code, we get

The value in itarget is 42 using the pointer it is 42
The value in ftarget is 2.4 using the pointer it is 2.4
The value in ctarget is c using the pointer it is c
Which is what we would expect.

Pointers and Arrays

In the terms we have been using, a pointer is used to hold the location of another variable. The contents property of the pointer contains the location property of the other variable. So in the code below,
int a=5, *ptr;
ptr = &a;
The ampersand extracts the location from the a variable and puts it in as the contents of the ptr variable.

Lets look at some concrete examples. Here are the properties of a and ptr

Name Type Location Contents
a int 22222 5
ptr int * 33333 22222

So the pointer variable now contains the address of the a variable. We can use this to get and set the contents of a. There is an operator that is used to retrieve the contents of the thing pointed at. For example, if we print ptr like this
cout << ptr << endl;
We get the value 22222 printed. But we are not really interested in this value. We are interested in the entry in the table above that has a location value of 22222. We use the star * operator to do this,
cout << *ptr << endl;
would print 5.

One way to view this is that the * operator searches this table and looks for the row where the location field matches the contents of the pointer variable. Another way is to see it as just getting the contents indirectly. * ptr works in two steps. It first gets the contents of pointer, which is 22222. Then it uses that number as a memory address and retrieves whatever is stored there. In this case, that is 5. This is because the location value is the actual machine address where the variables contents are stored.

Now let's look at the relationship between arrays and pointers. An array van be seen as a collection of individual variables. For example,
int foo[3];
This is three variables collected together. They are named foo[0]. foo[1] and foo[2]. These can be used anywhere an integer can be used. Another analogy is to a carton of eggs. The individual eggs can be used in any recipe that calls for eggs. But the carton cannot. Consider a function like this.


int
bar(int a) {
   cout << a << endl;
} // bar

so a function call like
b = bar(foo[1]);
is legal but
b = bar(foo);
is not.
Now this variable
int *ptr;
Let's point this at the first element in the array
ptr = &foo[0];
now we have two ways to refer to the same memory location. Both *ptr and foo[0] refer to the same thing. We can use them interchangeably.
*ptr = 8;   foo[0] = 8;
x = *ptr; x = foo[0];
In C++, this two ways of getting to a memory address can be used mostly the same. So we can say
ptr[0] = 8; *foo = 8;

Notice that we have used the name of the array as a pointer. This is generally true and is more than a notation. The name of an array is a pointer to the array. So instead of saying
ptr = &foo[0];
we could have said
ptr = foo;

The contents of ptr is the same as foo. And the contents of foo is the address of the first element of foo. Now consider what would happen if we added one to the contents of ptr. Since all elements of an array are physically right next to each other, adding one to an address gives us the next address. So, if ptr points to the first element of foo, ptr + 1, points to the second. And ptr + 2 points to the third. Now if we put a * in front like this,
*ptr + 1
this would get the value stored at that location. But not quite. Due to precedence rules, * is done before +, so this actually gets the value stored at ptr and adds one to that. Not quite what we want. So we use parenthesis to sort it out,
*(ptr+1)

This adds one to ptr, and retrieves the value stored there. This is exactly that same as ptr[0]. In fact, that is what happened when the compiler sees ptr[1].

This operation is called pointer arithmetic and is not quite the same as regular arithmetic. We can actually see that here. An integer is actually at least 2 bytes and more often 4 bytes long. Adding 1 to a pointer (memory address) would normally get us the address of the next byte. We want the address of the next integer. The compiler knows this and translates the arithmetic to it actually adds 1 times the size of the thing being pointed to. So ptr + 1 is really ptr + 4. You never have to worry about this translation. It happens automatically.

We can now pass arrays into functions. If we write the function to take a pointer to an array, we can pass the array into the function.
void
nyarf(int * p) {
   cout << p[0] << endl;
   cout << *p << endl;
} // nyarf

We can call this function like this,
nyarf(foo);
This works since the type of foo, by itself, is pointer to int. This is because the name of an array is a pointer to the base type. We could also call this as
nyarf(ptr);
since ptr points at foo.