Next Up Previous Hi Index

Chapter 16

Pointers and References

(original text by Paul Bui)

I suppose the easiest way to explain pointers and references is to jump right into an example. Let's first take a look at some algebra:

x = 1

In algebra, when you use a variable, it is essentially a letter or designation that you use to refer to some number. In programming, the variable in the equation above must be on the left side. You've probably noticed by now that the compiler won't let you do something like this:

1 = x;

And if you didn't know this... now you know, and knowing is half the battle. The reason why you receive a compile-time error like "lvalue required in..." is because the left-hand side of the equation, traditionally referred to as the lvalue, must be an address in memory. Think about it for a second. If you wanted to store some data somewhere, you first need to know where you're going to store it before the action can take place. The lvalue is the address of the place in memory where you're going to store the information and/or data of the right-hand side of the equation, also known as the rvalue.

In C++, you will most likely at one point or another, deal with memory management. To manipulate addresses, C++ has two mechanisms: pointers and references.


16.1 What are pointers and references?

Pointers and references are essentially variables that hold memory addresses as their values. You learned before about the various different data types such as: int, double, and char. Pointers and references hold the addresses in memory where you find the data of the various data types that you have declared and assigned. The two mechanisms, pointers and references, have different syntax and different traditional uses.


16.2 Declaring pointers and references

When declaring a pointer to an object or data type, you basically follow the same rules of declaring variables and data types that you have been using, only now, to declare a pointer of SOMETYPE, you put an asterisk * in front of the variable:

SOMETYPE* somevar;

int* x;

To declare a reference, you do the exact same thing you did to declare a pointer, only this time, rather than using an asterisk *, use instead an ampersand &:

SOMETYPE& somevar;

int& x;

This is most commonly used for reference parameters (Section 8.7), but as you will learn in a moment, there are also reference variables

You have already learned that spacing in C++ does not matter, so the following pointer declarations are identical:

SOMETYPE*  somevar;
SOMETYPE * some
var;
SOMETYPE  *some
var;

The following reference declarations are identical as well:

SOMETYPE&  somevar;
SOMETYPE & some
var;
SOMETYPE  &somevar;

Different programmers write the declarations in different ways. In many respects, it makes more sense to think of the * or & as going with the type.  For example, we can think of

int* x;

as declaring x to be an "integer pointer," that is, a pointer to an integer. However, the * or & actually goes with the variable, as we can see from a declaration such as:

int* x, y;

This declares x to be a pointer to an integer but y to be an ordinary integer variable, as is more obvious if we write it:

int *x, y;

Usually I will write the * or & next to the type, since it's easier to understand, but it's important to remember that it really goes with the variable or parameter name.


16.3 The "address of" and "value at address" operators

Although declarations of pointers and references look similar, assigning them is a whole different story. In C++, there is another operator that you'll get to know intimately, the "address of" operator, which is also denoted by the ampersand & symbol. The "address of" operator does exactly what it says, it returns the "address of" a variable, a symbolic constant, or an element in an array, in the form of a pointer of the corresponding type.  (In effect, it converts an lvalue into an rvalue.) To use the "address of" operator, you tack it on in front of the variable that you wish to have the address of.

SOMETYPE* x = &somevar; // must be used as rvalue

This puts the address of somevar into the pointer variable x. Don't confuse the "address of" operator with the declaration of a reference. Because use of operators is restricted to rvalues, or to the right hand side of the assignment, the compiler knows that &somevar is the "address of" operator being used to denote the address of somevar as a pointer value.

The * operator also has a second use: to fetch the contents of a memory location pointed at by a pointer variable.  For example, if you declare:

int x = 17;
int* xp = &x;

The value of variable xp is a pointer to memory location, namely that at which x is stored.  To find the contents of the location to which xp points, we need to "dereference" it with the * operator.  For example,

cout << *xp << endl;

prints 17.  Thus "*xp" may be verbalized, "the value at address xp." 

If you have a function which has a pointer as an argument, you may use the "address of" operator on a variable that you want to pass to the function. You do not necessarily have to declare a separate pointer variable just to use it as an argument in a function; the "address of" operator returns a pointer and thus can be used in that case too:

SOMETYPE MyFunc(SOMETYPE *x)
{
  cout << *x << endl;
}

int main()
{
  SOMETYPE i;

  MyFunc(&i);

  return 0;
}

16.4 Assigning to pointers and references

As you saw in the syntax of the "address of" operator, a pointer can be assigned the value returned by the "address of" operator, which is a pointer. To assign to a pointer variable, it must be given an address in memory as the rvalue, or the compiler will give you an error.

int x;
int* px = &x;

The above piece of code declares a variable x of type int and then declares an int pointer variable px, which is initialized to the address in memory of x. The pointer px essentially "points" to x by storing its address in memory. Keep in mind that when declaring a pointer, the pointer needs to be of the same base type as the variable or constant from which you take the address.

Now here is where you begin to see the differences between pointers and references. To assign to a pointer variable an address in memory, you had to have used the "address of" operator to return the address in memory of the variable as a pointer. A reference however, does not need to use the "address of" operator to be assigned to an address in memory. To assign an address in memory of a variable to a reference, you just need to use the variable as the rvalue.

int x;
int& rx = x;

The above piece of code declares a variable x of type int and then declares an int reference rx, which is defined to "refer to" x. Notice how the address of x is stored in rx, or "referred to" by rx without the use of any operators, just the variable. In effect rx is an alias for x, and can be used in any way x can be used. For example,

x = 43;
rx = 17;

cout << rx << "=" << x << endl;

prints "17=17". You must also follow the same rule as pointers, wherein you must declare the same type reference as the variable or constant to which you refer.

Hypothetically, if you wanted to see what output a pointer would be...

#include <iostream>
using namespace std;

int main()
{
  int someNumber = 12345;
  int* ptrSomeNumber = &someNumber;

  cout << "someNumber = " << someNumber << endl;
  cout << "ptrSomeNumber = " << ptrSomeNumber << endl;

  return 0;
}

If you compiled and ran the above code, you would have the variable someNumber output 12345 while ptrSomeNumber would output some hexadecimal number (addresses in memory are represented in hexadecimal notation). Now, if you wanted to output the value pointed to by ptrSomeNumber, you would use this code:

#include <iostream>

int main()
{
  int someNumber = 12345;
  int* ptrSomeNumber = &someNumber;

  cout << "someNumber = " << someNumber << endl;
  cout << "ptrSomeNumber points to " << *ptrSomeNumber << endl;

  return 0;
}

So basically, when you want to use, modify, or manipulate the value pointed to by pointer x, you denote the value/variable with *x.

Here is a quick list of things you can do with pointers and references:


16.5 The NULL pointer

If a pointer is assigned the null pointer, it points to nothing. The null pointer is denoted by 0 or—more readably—by NULL (a name that is defined in several of the standard C++ libraries, including iostream). The NULL pointer is often used in conditions and/or in logical operations.

#include <iostream>

int main()
{
  int x = 12345;
  int* px = &x;

  while (px != NULL) {
    cout << "Pointer px points to something\n";
    px = NULL;
  }

  cout << "Pointer px points to null, nothing, nada!\n";

  return 0;
}

If pointer px is not NULL, then it is pointing to something, however, if the pointer is NULL, then it is pointing to nothing. The null pointer becomes very useful when you must test the state of a pointer, whether it has a value or not.


16.6 Dynamic Memory Allocation

You may have wondered how programmers allocate memory efficiently without knowing, prior to running the program, how much memory will be necessary. This is accomplished with dynamic memory allocation.

You have learned about assigning to pointers using the "address of" operator, which returns the address of a variable or constant in the form of a pointer, but C++ has another operator that returns a pointer, the new operator. The new operator allows the programmer to allocate memory for a specific data type, structure, class, etc, and gives the programmer the address of that allocated area of memory in the form of a pointer. The new operator is used as an rvalue, similar to the "address of" operator. Take a look at the code below to see how the new operator works.

int n = 10;
SOMETYPE *pS;
int *pint;
vector<double> *pvec;

pS = new SOMETYPE;
pint = new int;
pvec = 
new vector<double> (1000);
*pint = n;
(*pvec)[0] = *pint;

By assigning to a pointer variable the address of an allocated area of memory, rather than having to use a variable declaration, you basically override the "middleman" (the variable declaration). Now, you can allocate memory dynamically without having to know the number of variables you should declare. If you looked at the above piece of code, you can use the new operator to allocate memory for vectors too, which comes quite in handy when we need to manipulate efficiently the sizes of large vectors, structures, classes, or other objects.  Notice that to index a vector pointer, you have to surround the vector value in parentheses.

The memory that your pointer points to (as a result of a new operator) can also be "deallocated," not destroyed but, rather, freed up so that it can be used for other purposes (e.g., reallocated by subsequent new operations). The delete operator frees up the address in memory to which the pointer is pointing:

delete pvec;
delete pint;

The memory pointed to by pvec and pint have been freed up, which is good, because if you do not free up memory when you are done with it, then it cannot be allocated for other purposes.  Failure to deallocate unused memory is called a memory leak, and can cause a program to run out of memory; the program's space will be filled with unusable "garbage."  (So, you can see that recycling is as important in programs as in the environment!) Essentially, every time you use the new operator on something, you should use the delete operator to free that memory when it is no longer needed. On the other hand, it is important that you do not delete an object that it still in use.  This can happen if you have two or more pointers to the same memory area and you delete one of them.  Now the system thinks this memory area is available for reuse, but you still have another, "dangling" pointer to it.  As a result, two parts of your program may end up using the same memory for two different, incompatible purposes. This is a common source of obscure, hard to diagnose program errors, and often results in programs that crash. Avoiding memory leaks on one side and dangling pointers on the other requires careful program design and documentation, so that you know for sure when you can and should delete a pointer.

The new and delete operators do not have to be used in conjunction with each other within the same function or block of code. It is proper and often advisable to write some functions that allocate memory and other functions that deallocate memory.


16.7 Returning pointers and/or references from functions

When declaring a function, you must declare it in terms of the type that it will return, for example:

int MyFunc();      // returns an int
SOMETYPE MyFunc(); // returns a SOMETYPE

int* MyFunc();      // returns a pointer to an int
SOMETYPE *MyFunc(); // returns a pointer to a SOMETYPE
SOMETYPE &MyFunc(); // returns a reference to a SOMETYPE

The above code shows how to declare a function that will return a reference or a pointer.  Next consider:

SOMETYPE *MyFunc(SOMETYPE *p)
{
   ...
   ...
   return p;
}

SOMETYPE &MyFunc(
SOMETYPE &r)
{
  ...
  ...
  return r;
}

Within the body of the function, the return statement should not return a pointer or a reference that has the address of a local variable that was declared within the function, because as soon as the function exits, all local variables are destroyed and your pointer or reference will be pointing to some place in memory that has been deallocated. Having a dangling pointer like that is quite dangerous.

However, within the body of your function, if your pointer or reference has the address in memory of a data type, structure, or class that you dynamically allocated the memory for, using the new operator, then returning said pointer or reference would be reasonable.

SOMETYPE *MyFunc()  //returning a pointer that has a dynamically
{                   //allocated memory address is proper code
   
SOMETYPE *p = new SOMETYPE;
   ...
   ...
   return p;
}

16.8 Pointers to structures and objects

It is frequently useful to dynamically allocate structures or objects (instances of classes).  For example, recall that in Section 13.1 I defined a Card structure to have two fields, rank and suit.  You can get a pointer to a new Card object with an assignment such as this:

Card* cardPtr = new Card (DIAMONDS, ACE);

Suppose we want to increase the rank of the card pointed to by cardPtr.  You cannot use cardPtr.rank, because cardPtr is of type Card*, not Card. Rather, you must dereference the pointer, which follows the pointer to the Card to which it points:

(*cardPtr).rank = Rank( (*cardPtr).rank + 1 );

This is correct, but it is much more common to use the arrow operator, a special abbreviation that C++ has for an instance variable in a structure pointed to by a pointer:

cardPtr->rank = Rank( cardPtr->rank + 1 );

In general, ptrvar->field is the lvalue of the instance variable field in the structure or object pointed to by pointer ptrvar. Similarly, if func is a member function of ptrvar, it is invoked by ptrvar->func(…).  Thus, to print the card at cardPtr you would write cardPtr->print().

16.9 Glossary

pointer
a variable that holds an address in memory. Similar to a reference, however, pointers have different syntax and traditional uses from references.
reference
a variable that holds an address in memory. Similar to a pointer, however, references have different syntax and traditional uses from pointers.
"address of" operator (&)
an operator that returns the address in memory of a variable.
"value at address" operator (*)
an operator that returns the contents of an address in memory.
"arrow" operator (->)
selects an instance variable or member function of a structure or object at an address.
dynamic memory allocation
the explicit allocation of contiguous blocks of memory at any time in a program.
new
an operator that returns a pointer, of the appropriate data type, to newly allocated memory.
delete
an operator that returns the memory pointed to by a pointer to the free store (a special pool of free memory).

Revised 2011-01-07.


Next Up Previous Hi Index