416 Pointers and Dynamic Arrays Pitfall D ANGLING P OINTERS When you apply delete to a pointer variable, the dynamic variable to which it is pointing is destroyed. At that point, the value of the pointer variable is undefined, which means that you do not know where it is pointing. Moreover, if some other pointer variable was pointing to the dynamic variable that was destroyed, then this other pointer variable is also undefined. These undefined pointer variables are called dd dd aa aa nn nn gg gg ll ll ii ii nn nn gg gg pp pp oo oo ii ii nn nn tt tt ee ee rr rr ss ss . If p is a dangling pointer and your pro- gram applies the dereferencing operator * to p (to produce the expression *p), the result is unpredictable and usually disastrous. Before you apply the dereferencing operator * to a pointer variable, you should be certain that the pointer variable points to some variable. C++ has no built-in test to check whether a pointer variable is a dangling pointer. One way to avoid dangling pointers is to set any dangling pointer variable equal to NULL. Then your pro- gram can test the pointer variable to see if it is equal to NULL before applying the dereferencing operator * to the pointer variable. When you use this technique, you follow a call to delete by code that sets all dangling pointers equal to NULL. Remember, other pointer variables may become dangling pointers besides the one pointer variable used in the call to delete, so be sure to set all dangling pointers to NULL. It is up to the programmer to keep track of dangling pointers and set them to NULL or otherwise ensure that they are not dereferenced. ■ DYNAMIC VARIABLES AND AUTOMATIC VARIABLES Variables created with the new operator are called dynamic variables (or dynamically allo- cated variables) because they are created and destroyed while the program is running. Local variables—that is, variables declared within a function definition—also have a certain dynamic characteristic, but they are not called dynamic variables. If a variable is local to a function, then the variable is created by the C++ system when the function is called and is destroyed when the function call is completed. Since the main part of a program is really just a function called main, this is even true of the variables declared in the main part of your program. (Since the call to main does not end until the program T HE delete O PERATOR The delete operator eliminates a dynamic variable and returns the memory that the dynamic variable occupied to the freestore. The memory can then be reused to create new dynamic vari- ables. For example, the following eliminates the dynamic variable pointed to by the pointer variable p: delete p; After a call to delete, the value of the pointer variable, like p above, is undefined. (A slightly different version of delete, discussed later in this chapter, is used when the dynamically allo- cated variable is an array.) dangling pointer Pointers 417 Tip ends, the variables declared in main are not destroyed until the program ends, but the mechanism for handling local variables is the same for main as for any other function.) These local variables are sometimes called automatic variables because their dynamic properties are controlled automatically for you. They are automatically created when the function in which they are declared is called and automatically destroyed when the function call ends. Variables declared outside any function or class definition, including outside main, are called global variables. These global variables are sometimes called statically allo- cated variables, because they are truly static in contrast to dynamic and automatic variables. We discussed global variables briefly in Chapter 3. As it turns out, we have no need for global variables and have not used them. 4 D EFINE P OINTER T YPES You can define a pointer type name so that pointer variables can be declared like other variables without the need to place an asterisk in front of each pointer variable. For example, the following defines a type called IntPtr, which is the type for pointer variables that contain pointers to int variables: typedef int* IntPtr; Thus, the following two pointer variable declarations are equivalent: IntPtr p; and int *p; You can use typedef to define an alias for any type name or definition. For example, the follow- ing defines the type name Kilometers to mean the same thing as the type name double: typedef double Kilometers; Once you have given this type definition, you can define a variable of type double as follows: Kilometers distance; Renaming existing types this way can occasionally be useful. However, our main use of typedef will be to define types for pointer variables. Keep in mind that a typedef does not produce a new type but is simply an alias for the type definition. For example, given the previous definition of Kilometers, a variable of type 4 Variables declared within a class using the modifier static are static in a different sense than the dynamic/static contrast we are discussing in this section. automatic variable global variable typedef 418 Pointers and Dynamic Arrays Self-Test Exercises Kilometers may be substituted for a parameter of type double. Kilometers and double are two names for the same type. There are two advantages to using defined pointer type names, such as IntPtr defined previ- ously. First, it avoids the mistake of omitting an asterisk. Remember, if you intend p1 and p2 to be pointers, then the following is a mistake: int *p1, p2; Since the * was omitted from the p2, the variable p2 is just an ordinary int variable, not a pointer variable. If you get confused and place the * on the int, the problem is the same but is more difficult to notice. C++ allows you to place the * on the type name, such as int, so that the following is legal: int* p1, p2; Although the above is legal, it is misleading. It looks like both p1 and p2 are pointer variables, but in fact only p1 is a pointer variable; p2 is an ordinary int variable. As far as the C++ compiler is concerned, the * that is attached to the identifier int may as well be attached to the identifier p1. One correct way to declare both p1 and p2 to be pointer variables is int *p1, *p2; An easier and less error-prone way to declare both p1 and p2 to be pointer variables is to use the defined type name IntPtr as follows: IntPtr p1, p2; The second advantage of using a defined pointer type, such as IntPtr, is seen when you define a function with a call-by-reference parameter for a pointer variable. Without the defined pointer type name, you would need to include both an * and an & in the declaration for the function, and the details can get confusing. If you use a type name for the pointer type, then a call-by-reference parameter for a pointer type involves no complications. You define a call-by-reference parameter for a defined pointer type just like you define any other call-by-reference parameter. Here’s an example: void sampleFunction(IntPtr& pointerVariable); 5. What unfortunate misinterpretation can occur with the following declaration? int* intPtr1, intPtr2; 6. Suppose a dynamic variable were created as follows: char *p; p = new char; Pointers 419 Pitfall Assuming that the value of the pointer variable p has not changed (so it still points to the same dynamic variable), how can you destroy this new dynamic variable and return the memory it uses to the freestore manager so that the memory can be reused to create other new dynamic variables? 7. Write a definition for a type called NumberPtr that will be the type for pointer variables that hold pointers to dynamic variables of type double. Also, write a declaration for a pointer variable called myPoint, which is of type NumberPtr. 8. Describe the action of the new operator. What does the new operator return? What are the indications of errors? P OINTERS AS C ALL - BY -V ALUE P ARAMETERS When a call-by-value parameter is of a pointer type, its behavior can occasionally be subtle and troublesome. Consider the function call shown in Display 10.4. The parameter temp in the function sneaky is a call-by-value parameter, and hence it is a local variable. When the function is called, the value of temp is set to the value of the argument p and the function body is executed. Since temp is a local variable, no changes to temp should go outside the function sneaky. In particu- lar, the value of the pointer variable p should not be changed. Yet the sample dialogue makes it look like the value of the pointer variable p has changed. Before the call to the function sneaky, the value of *p was 77, and after the call to sneaky the value of *p is 99. What has happened? T YPE D EFINITIONS You can assign a name to a type definition and then use the type name to declare variables. This is done with the keyword typedef. These type definitions are normally placed outside the body of the main part of your program and outside the body of other functions, typically near the start of a file. That way the typedef is global and available to your entire program. We will use type definitions to define names for pointer types, as shown in the example below. S YNTAX typedef Known_Type_Definition New_Type_Name ; E XAMPLE typedef int* IntPtr; The type name IntPtr can then be used to declare pointers to dynamic variables of type int, as in the following example: IntPtr pointer1, pointer2; 420 Pointers and Dynamic Arrays The situation is diagrammed in Display 10.5. Although the sample dialogue may make it look as if p were changed, the value of p was not changed by the function call to sneaky. Pointer p has two things associated with it: p’s pointer value and the value stored where p points. But the value of p is the pointer (that is, a memory address). After the call to sneaky, the variable p contains the same pointer value (that is, the same memory address). The call to sneaky has changed the value of the variable pointed to by p, but it has not changed the value of p itself. If the parameter type is a class or structure type that has member variables of a pointer type, the same kind of surprising changes can occur with call-by-value arguments of the class type. How- ever, for class types, you can avoid (and control) these surprise changes by defining a copy con- structor , as described later in this chapter. Display 10.4 A Call-by-Value Pointer Parameter (part 1 of 2) 1 //Program to demonstrate the way call-by-value parameters 2 //behave with pointer arguments. 3 #include <iostream> 4 using std::cout; 5 using std::cin; 6 using std::endl; 7 typedef int* IntPointer; 8 void sneaky(IntPointer temp); 9 int main( ) 10 { 11 IntPointer p; 12 p = new int; 13 *p = 77; 14 cout << "Before call to function *p == " 15 << *p << endl; 16 sneaky(p); 17 cout << "After call to function *p == " 18 << *p << endl; 19 return 0; 20 } Pointers 421 ■ USES FOR POINTERS Chapter 17 discusses ways to use pointers to create a number of useful data structures. This chapter only discusses one use of pointers, namely, to reference arrays, and in par- ticular to create and reference a kind of array known as a dynamically allocated array. Dynamically allocated arrays are the topic of Section 10.2. Display 10.4 A Call-by-Value Pointer Parameter (part 2 of 2) 21 void sneaky(IntPointer temp) 22 { 23 *temp = 99; 24 cout << "Inside function call *temp == " 25 << *temp << endl; 26 } S AMPLE D IALOGUE Before call to function *p == 77 Inside function call *temp == 99 After call to function *p == 99 Display 10.5 The Function Call sneaky(p); 1. Before call to sneaky: p 77 2. Value of p is plugged in for temp: p temp 77 4. After call to sneaky:3. Change made to *temp: p temp 99 p 99 422 Pointers and Dynamic Arrays Dynamic Arrays In this section you will see that array variables are actually pointer variables. You will also find out how to write programs with dynamically allocated arrays. A dynamically allocated array (also called simply a dynamic array) is an array whose size is not spec- ified when you write the program, but is determined while the program is running. ■ ARRAY VARIABLES AND POINTER VARIABLES Chapter 5 described how arrays are kept in memory. At that point we discussed arrays in terms of memory addresses. But a memory address is a pointer. So, in C++ an array vari- able is actually a kind of pointer variable that points to the first indexed variable of the array. Given the following two variable declarations, p and a are both pointer variables: int a[10]; typedef int* IntPtr; IntPtr p; The fact that a and p are both pointer variables is illustrated in Display 10.6. Since a is a pointer that points to a variable of type int (namely, the variable a[0]), the value of a can be assigned to the pointer variable p as follows: p = a; After this assignment, p points to the same memory location that a points to. Thus, p[0], p[1], . . . p[9] refer to the indexed variables a[0], a[1], . . . a[9]. The square bracket notation you have been using for arrays applies to pointer variables as long as the pointer variable points to an array in memory. After the above assignment, you can treat the identifier p as if it were an array identifier. You can also treat the identifier a as if it were a pointer variable, but there is one important reservation: You cannot change the pointer value in an array variable. If the pointer variable p2 has a value, you might be tempted to think the following is legal, but it is not: a = p2;//ILLEGAL. You cannot assign a different address to a. The underlying reason why this assignment does not work is that an array variable is not of type int*, but its type is a const version of int*. An array variable, like a, is a pointer variable with the modifier const, which means that its value cannot be changed. (An array variable is actually more than an ordinary pointer variable since it carries some additional size information about the array, but an array variable does include a pointer to the array and the array variable can be assigned to a pointer variable. So, an array variable is a kind of pointer variable and can be treated like a pointer variable whose value cannot be changed.) 10.2 dynamically allocated array Dynamic Arrays 423 ■ CREATING AND USING DYNAMIC ARRAYS One problem with the kinds of arrays we discussed in Chapter 5 is that you must spec- ify the size of the array when you write the program—but you may not know what size array you need until the program is run. For example, an array might hold a list of stu- dent identification numbers, but the size of the class may be different each time the Display 10.6 Arrays and Pointer Variables 1 //Program to demonstrate that an array variable is a kind of pointer variable. 2 #include <iostream> 3 using std::cout; 4 using std::endl; 5 typedef int* IntPtr; 6 int main( ) 7 { 8 IntPtr p; 9 int a[10]; 10 int index; 11 for (index = 0; index < 10; index++) 12 a[index] = index; 13 p = a; 14 for (index = 0; index < 10; index++) 15 cout << p[index] << " "; 16 cout << endl; 17 for (index = 0; index < 10; index++) 18 p[index] = p[index] + 1; 19 for (index = 0; index < 10; index++) 20 cout << a[index] << " "; 21 cout << endl; 22 return 0; 23 } S AMPLE D IALOGUE 0 1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 7 8 9 10 Note that changes to the array p are also changes to the array a. 424 Pointers and Dynamic Arrays program is run. With the kinds of arrays you have used thus far, you must estimate the largest possible size you may need for the array and hope that size is large enough. There are two problems with this. First, you may estimate too low, and then your pro- gram will not work in all situations. Second, since the array might have many unused positions, this can waste computer memory. Dynamically allocated arrays avoid these problems. If your program uses a dynamically allocated array for student identification numbers, then the number of students can be entered as input to the program and the dynamically allocated array can be created to have a size exactly equal to the number of students. Dynamically allocated arrays are created using the new operator. The creation and use of dynamically allocated arrays is surprisingly simple. Since array variables are pointer variables, you can use the new operator to create dynamically allocated variables that are arrays and can treat these dynamically allocated arrays as if they were ordinary arrays. For example, the following creates a dynamically allocated array variable with ten array elements of type double: typedef double* DoublePtr; DoublePtr d; d = new double[10]; To obtain a dynamically allocated array of elements of any other type, simply replace double with the desired type. In particular, you can replace the type double with a struct or class type. To obtain a dynamically allocated array variable of any other size, simply replace 10 with the desired size. There are also a number of less-obvious things to notice about this example. First, the pointer type that you use for a pointer to a dynamically allocated array is the same as the pointer type you would use for a single element of the array. For instance, the pointer type for an array of elements of type double is the same as the pointer type you would use for a simple variable of type double. The pointer to the array is actually a pointer to the first indexed variable of the array. In the above example, an entire array with ten indexed variables is created, and the pointer p is left pointing to the first of these ten indexed variables. Second, notice that when you call new, the size of the dynamically allocated array is given in square brackets after the type, which in this example is the type double. This tells the computer how much storage to reserve for the dynamic array. If you omitted the square brackets and the 10 in this example, the computer would allocate enough storage for only one variable of type double, rather than for an array of ten indexed variables of type double. Display 10.7 contains a program that illustrates the use of a dynamically allocated array. The program searches a list of numbers stored in a dynamically allocated array. The size of the array is determined when the program is run. The user is asked how many numbers there will be, and then the new operator creates a dynamically allocated array of that size. The size of the dynamic array is given by the variable arraySize. The size of a dynamic array need not be given by a constant. It can, as in Display 10.7, be given by a variable whose value is determined when the program is run. creating a dynamic array Dynamic Arrays 425 Display 10.7 A Dynamically Allocated Array (part 1 of 2) 1 //Searches a list of numbers entered at the keyboard. 2 #include <iostream> 3 using std::cin; 4 using std::cout; 5 typedef int* IntPtr; 6 void fillArray(int a[], int size); 7 //Precondition: size is the size of the array a. 8 //Postcondition: a[0] through a[size-1] have been 9 //filled with values read from the keyboard. 10 int search(int a[], int size, int target); 11 //Precondition: size is the size of the array a. 12 //The array elements a[0] through a[size-1] have values. 13 //If target is in the array, returns the first index of target. 14 //If target is not in the array, returns -1. 15 int main( ) 16 { 17 cout << "This program searches a list of numbers.\n"; 18 int arraySize; 19 cout << "How many numbers will be on the list? "; 20 cin >> arraySize; 21 IntPtr a; 22 a = new int[arraySize]; 23 fillArray(a, arraySize); 24 int target; 25 cout << "Enter a value to search for: "; 26 cin >> target; 27 int location = search(a, arraySize, target); 28 if (location == -1) 29 cout << target << " is not in the array.\n"; 30 else 31 cout << target << " is element " << location << " in the array.\n"; 32 33 delete [] a; 34 35 return 0; 36 } Ordinary array parameters The dynamic array a is used like an ordinary array. . function, then the variable is created by the C++ system when the function is called and is destroyed when the function call is completed. Since the main part of a program is really just a function. determined when the program is run. creating a dynamic array Dynamic Arrays 425 Display 10.7 A Dynamically Allocated Array (part 1 of 2) 1 //Searches a list of numbers entered at the keyboard. 2. is really just a function called main, this is even true of the variables declared in the main part of your program. (Since the call to main does not end until the program T HE delete O PERATOR The