In order to improve our container class implementations, we need to know about pointers. A pointer is the memory address of a variable. To understand this def-inition, you need a mental picture of the computer’s memory as consisting of numbered memory locations (called bytes). Each variable in a program is stored in a sequence of adjacent bytes. For example, on some machines each integer variable requires four bytes. On such a machine, an integer declaration
such as xxx
Pointers and Dynamic Memory 155
the size of each bag will be independent of the other bags
such as provides four adjacent bytes of memory to store the value of the integer i. The example drawn here provides bytes numbered 990 through 993 for an integer variablei.
The numbers labeling each byte are called the memory addresses. When a variable occupies several adjacent bytes, then the memory address of the first byte is also called the memory address of the variable. So in our example, the address of the integer i is 990. The address of a variable is called a pointer. These addresses are called pointers because they can be thought of as “pointing” to a variable. The address “points” to the variable because it identifies the vari-able by telling where the varivari-able is, rather than telling what the variable’s name is. Our integer i can be pointed out by saying, “It’s over there at location 990.”
int i;
Pointer Variables
Pointers are much more useful than mere indications of variable locations. To begin to see the utility of pointers, we must look at variables that are designed to store pointers. A variable to store a pointer must be declared as a special pointer variable by placing an asterisk before the pointer variable’s name. The complete declaration of a pointer variable must contain three items, as shown here:
double *my_first_ptr;
declaring pointer variables
In our example, my_first_ptr is a pointer variable that is capable of pointing to any double variable. In other words, my_first_ptr can hold the memory address of a double variable. The pointer variable my_first_ptr does not actu-ally contain a double number itself—it merely contains the address of a double variable. Also, since we used the double data type in our declaration,
my_first_ptr cannot contain a pointer to a variable of some other type, such as
int or char. It may contain only a pointer to a double variable.
If you declare several pointer variables on a single line, then an asterisk must appear before each variable name. For example, to declare two pointers to char-acters:
char *c1_ptr, *c2_ptr;
If you omit the asterisk before c2_ptr, then c2_ptr will be an ordinary charac-ter variable rather than a poincharac-ter to a characcharac-ter. For additional clarity we often use “_ptr” as the end of a pointer variable’s name, or we use “cursor” as part of a pointer variable’s name because a “cursor” means a pointer that “runs through a structure.”
the type of data that the pointer variable can
point to an asterisk
the name of the newly declared pointer variable
Pointer Variable Declarations
A variable that is a pointer to other variables of type Type_Name is declared in the same way that you declare a variable of type Type_Name, except that you place an asterisk at the beginning of the variable name.
Syntax:
Type_Name *var_name1;
Examples:
int *cursor;
char *c1_ptr, *c2_ptr;
declaring two char pointers
Pointers and Dynamic Memory 157
Of course, a pointer variable is of no use unless there is something for it to point to. For example, consider these two declarations:
int *example_ptr;
int i;
We can make example_ptr contain the address of i by using the & operator shown here:
example_ptr = &i;
the & operator The& operator, called the address operator, provides the address of a variable;
for instance, &i is “the address of the integer variable i.” So the assignment statement places “the address of i” into example_ptr. Or we could simply say thatexample_ptr now “points to” i.
the * operator After the assignment, you have two ways to refer to i: You can call it i, or
you can call it “the variable pointed to by example_ptr.” In C++ “the variable pointed to by example_ptr” is written *example_ptr. This is the same asterisk notation that we used to declare *example_ptr, but now it has yet another meaning. When the asterisk is used in this way, it is called the dereferencing operator, and the pointer variable is said to be dereferenced. For example:
i = 42;
example_ptr = &i;
cout << i << endl;
cout << << endl;
The dereferencing operator can produce some surprising results. Consider the following code:
i = 42;
example_ptr = &i;
*example_ptr = 0;
cout << *example_ptr << endl;
cout << i << endl;
As long as example_ptr contains a pointer to i, then i and *example_ptr refer to the same variable. So when you set *example_ptr equal to 0, you are really settingi equal to 0.
The symbol & that is used as the address operator is the same symbol that is used in a function’s parameter list to specify a reference parameter (see page 69).
This is more than a coincidence. The implementation of a reference parameter is accomplished by using the address of the actual argument, rather than making a completely separate copy (as a value parameter does). These two usages of the symbol & are much the same, but since they are slightly different, we will consider them to be two different (but closely related) usages of the symbol &.
This statement puts the address of i into the pointer variable example_ptr.
Both statements print 42.
This dereferences example_ptr.
*example_ptr
This prints 0.
This also prints 0.
Using the Assignment Operator with Pointers
You can copy the value of one pointer variable to another with the usual assign-ment operator. For example:
int i = 42;
int *p1;
int *p2;
p1 = &i;
cout << *p1 << endl;
cout << *p2 << endl;
When dealing with pointer variables, there is a critical distinction between a pointer variable (such as p1) and the thing it points to (such as *p1). Do not con-fuse the meaning of these two assignment statements:
p2 = p1; versus *p2 = *p1;
As we have seen, means “make p2 point to the same variable that p1 is already pointing to.” On the other hand, the inclusion of the dereferencing aster-isks in gives the statement quite a different meaning. The new meaning is to “copy the value from the variable that p1 points to, to the variable thatp2 points to.” Here is an example, which starts by declaring two integers and two pointers to integers:
int i = 42;
int j = 80;
int *p1;
int *p2;
p1 = &i;
p2 = &j;
p1 now points to i.
Now p2 also points to i.
So, both statements will print 42.
p2 = p1;
The highlighted assignment statement says “make p2 point to the same variable that p1 is already pointing to.”
A pointer variable is usually drawn as a box containing an arrow. The arrow points to the variable whose address is stored in the pointer. For example, after the assignment , we would draw the picture shown in the margin. There are now three names for the variable i: You can call it i, or you can call it*p1, or you can call it *p2.
p2 = p1 int i
int *p1 int *p2 42
After the statements:
p1 = &i;
p2 = p1;
p2 = p1
*p2 = *p1
int i 42
int j 80
int *p1
int *p2 After the statements, the
pointer variables are as shown to the right.
Pointers and Dynamic Memory 159
Once the two pointers are initialized, we can see the effect of an assignment statement:
*p2 = *p1;
The assignment statement has copied the value 42 from the variable that p1 points to, to the variable that p2 points to. In effect, j has changed its contents, but the pointers themselves still point to separate memory locations.
Dynamic Variables and the new Operator
Pointers may point to ordinary variables, such as i in our previous example. But the real power of pointers arises when pointers are used with special kinds of variables called dynamically allocated variables, or more simply, dynamic variables. Dynamic variables are like ordinary variables, with two important differences:
1. Dynamic variables are not declared. A program may use many dynamic variables, but the dynamic variables never appear in any declaration the way an ordinary variable does. Moreover, a dynamic variable has no iden-tifier (such as the ideniden-tifiers i and j that are used in our examples).
2. Dynamic variables are created during the execution of a program. Only at that time does a dynamic variable come into existence.
To create a dynamic variable while a program is running, C++ programs use an operator called new, as shown here:
Pointer Variables Used with =
If p1 and p2 are pointer variables, then the assignment changes p2 so that it points to the same variable thatp1 already points to.
On the other hand, the assignment copies the value from the variable that p1 points to, to the variable that p2 points to—but the pointers p1 and p2 still point to the memory locations that they pointed to before the assignment statement.
int i
int *p1 42
int j
int *p2 42
After this assignment statement, the pointer variables still point to different locations, but the contents of one of those locations has changed.
p2 = p1
*p2 = *p1
double *d_ptr;
In this example, the new operator creates a new dynamic variable of type
double and returns a pointer to this new dynamic variable. The pointer is assigned to the pointer variable d_ptr. The creation of new dynamic variables is called memory allocation and the memory is dynamic memory, so we may say that “d_ptr points to a newly allocated double variable from dynamic memory.”
Here is another example, which allocates a new int variable:
int *p1;
p1 = new int;
*p1 = 42;
The assignment statement at the end of this example places 42 in the dynamic variable that p1 points to.
Using new to Allocate Dynamic Arrays
We have seen the new operator allocate one dynamic variable at a time. But in fact,new can allocate an entire array at once. The number of array components is listed in square brackets, immediately after the component data type, as shown here:
double *d_ptr;
Whennew allocates an entire array, it actually returns a pointer to the first com-ponent of the array. In our example, the new operator allocates an array of 10
double components and returns a pointer to the first component of the array.
The pointer is assigned to the pointer variable d_ptr. After the allocation, the array can be accessed by using array notation with the pointer variable d_ptr.
d_ptr = new double;
At this point, p1 is declared, but it has nothing to point to.
p1 ?
Now p1 is pointing to a newly allocated integer variable.
p1
A new integer is allocated by the new operator.
The new integer variable now p1 contains our favorite number, 42.
42
The new operator allocates an array of 10 double components and points d_ptr to the first component.
d_ptr = new double[10];
Pointers and Dynamic Memory 161
For example, the following statement will place 3.14 in the [9] component of the new array:
d_ptr[9] = 3.14;
Here is another example, which allocates a newint array:
int *p1;
p1 = new int[4];
p1[2] = 42;
The assignment statement at the end of this example places 42 in the [2] com-ponent of the array that p1 points to.
All the versions of new are summarized in Figure 4.1, including information about which constructor gets called when new allocates a new object of a class.
The array version of new is particularly useful because the number of array com-ponents can be calculated while the program is running. Therefore, the number of components can depend on factors such as user input. This is dynamic behavior—behavior that is determined when a program is running—and the arrays allocated by new are called dynamic arrays.
dynamic behavior is determined while a program is running As an example of dynamic behavior, consider a program that reads a list of
numbers and computes the average of the numbers. After computing the average, the program prints the list with an indication of which numbers are below the average and which are above. We would like this program to work for ten
num-Because d_ptr points to an array with 10 components, we can use array notation to access component [9].
At this point, p1 is declared, but it has nothing to point to.
p1 ?
Now p1 is pointing to a new array of four integers.
p1
[0] [1] [2] [3]
a newly allocated array of four integers
The [2] component of the array now contains 42.
p1
[0] [1] [2] [3]
42
bers, or a hundred numbers, or however many numbers we happen to have. The size of the array can be determined by user input, as shown here:
size_t array_size;
int *numbers;
cout << "How many numbers do you have? ";
cin >> array_size;
numbers =
We’ll fully develop this example in a moment, but first we need a closer look at memory allocation.
FIGURE 4.1 The new Operator
The new Operator
The new operator allocates memory for a dynamic variable of a specified type and returns a pointer to the newly allocated memory. For example, the following code allocates a new dynamic integer variable and sets p to point to this new variable:
int *p;
p = new int;
If the dynamic variable is an object of a class, then the default constructor will be called to initialize the new class instance. A different constructor will be called if you place the constructor’s arguments after the type name in the new statement. For example:
throttle *t_ptr;
t_ptr = new throttle(50);
Thenew operator can also allocate a dynamic array of components, returning a pointer to the first element. The size of the array is specified in square brackets after the data type of the components:
double *d_ptr;
d_ptr = new double[50];
d_ptr[3] = 3.14;
If the data type of the array component is a class, then the default constructor is used to initialize all components of the dynamic array. There is no mechanism to use a different constructor on the array components.
This calls the constructor with an integer argument.
This allocates a dynamic array of 50 doubles.
This assigns 3.14 to the [3] component of the array that d_ptr points to.
new int[array_size];
Pointers and Dynamic Memory 163
The Heap and the bad_alloc Exception
the heap When new allocates a dynamic variable or dynamic array, the memory comes
from a location called the program’s heap (also called the free store). Some computers provide huge heaps, more than a billion bytes. But even the largest heap can be exhausted by allocating too many dynamic variables. When the heap runs out of room, the new operator fails.
the bad_alloc exception Thenew operator indicates its failure by a mechanism called the bad_alloc
exception. Normally, an exception causes an error message to be printed and the program halts. Alternatively, a programmer can “catch” an exception and try to fix the problem, but we won’t discuss catching exceptions here.
Some older versions of C++ deal with new failure in a different way, by returning a special pointer value called the null pointer. This older behavior can still be obtained if the programmer writes the new operator in the form
new(nothrow) rather than simply new. The word nothrow is a constant in the header file <new>.
For us, the normal failure—resulting in an error message and halting—will be sufficient. We will, however, clearly document which functions use new so that more experienced programmers can deal with a bad_alloc in their own manner.
The delete Operator
The size of the heap varies from one computer to another. It could be just a few thousand bytes or more than a billion. Small programs are not likely to use all of the heap. However, even with small programs, it is an efficient practice to release any heap memory that is no longer needed. If your program no longer needs a dynamic variable, the memory used by that dynamic variable can be returned to the heap where it can be reused for more dynamic variables. In C++, thedelete operator is used to return the memory of a dynamic variable back to the heap. The delete operator is called by writing the word delete, followed by the pointer variable. An example appears at the top of the next page.
Failure of the new Operator
The new operator usually indicates failure by throwing an exception called the bad_alloc exception. Normally, an exception causes an error message to be printed and the program to halt. (Older C++ implementations use a different mechanism for new failure.)
We clearly document which functions use new so that experienced programmers can deal with the failure in their own manner.
int *example_ptr;
example_ptr = new int;
// Various statements that use *example_ptr appear here. When the // program no longer needs the dynamic variable that example_ptr // points to, the memory for that dynamic variable is returned to // the heap with the following statement:
After the delete statement, the memory that example_ptr was pointing to has been returned to the heap for reuse. Using the delete operation is called free-ing or releasfree-ing memory.
A slightly different version of delete is used to release a dynamic array. In this case, the square brackets [ ] appear between the word delete and the pointer variable’s name, as shown here:
int *example_ptr;
example_ptr = new int[50];
// Various statements that use the array example_ptr[. . .] appear // here. When the program no longer needs the dynamic array, the // memory for that dynamic array is returned to the heap with // the following statement:
Whendelete [ ] releases a dynamic array, there is no need for the array’s size inside the square brackets. The software that controls the heap automatically keeps track of the array’s size. Figure 4.2 on page 165 summarizes the delete
operator.
DEFINE POINTER TYPES
You can define a name for a pointer type so that pointer variables can be declared like other variables, without placing an asterisk in front of each pointer variable. For example, the following defines a data type called int_pointer, which is the type for pointer variables that point toint variables:
typedef int* int_pointer;
A type definition such as this usually appears in a header file or with the collection of function prototypes that precede a main program. After this type definition, the
declaration is equivalent to .
delete example_ptr;
delete [ ] example_ptr;
PR O G R A M M I N G TI P
int_pointer i_ptr; int *i_ptr;
Pointers and Dynamic Memory 165
Self-Test Exercises for Section 4.1
1. Describe two different uses of & in a C++ program.
2. Write two different statements that print the value of i after the follow-ing code has been executed:
int *int_ptr, i;
i = 30;
int_ptr = &i;
3. Write code that (1) allocates a new array of 1000 integers; (2) places the numbers 1 through 1000 in the components of the new array; and (3) returns the array to the heap.
4. How are dynamic variables different than ordinary variables?
5. What happens if the new operator fails to allocate memory from the heap?
FIGURE 4.2 The delete Operator
The delete Operator
Thedelete operator frees memory that has been used for dynamic variables. The memory is returned to the heap, where it can be reused at a later time. For example, the following code allocates an integer dynamic variable, and frees the memory when it is no longer needed:
Thedelete operator frees memory that has been used for dynamic variables. The memory is returned to the heap, where it can be reused at a later time. For example, the following code allocates an integer dynamic variable, and frees the memory when it is no longer needed: