Using const with Pointers

Recall that the const qualifier enables the programmer to inform the compiler that the value of a particular variable should not be modified.

Portability Tip 8.2

Although const is well defined in ANSI C and C++, some compilers do not enforce it properly. So a good rule is, "Know your compiler."

Over the years, a large base of legacy code was written in early versions of C that did not use const, because it was not available. For this reason, there are great opportunities for improvement in the software engineering of old (also called "legacy") C code. Also, many programmers currently using ANSI C and C++ do not use const in their programs, because they began programming in early versions of C. These programmers are missing many opportunities for good software engineering.

Many possibilities exist for using (or not using) const with function parameters. How do you choose the most appropriate of these possibilities? Let the principle of least privilege be your guide. Always award a function enough access to the data in its parameters to accomplish its specified task, but no more. This section discusses how to combine const with pointer declarations to enforce the principle of least privilege.


Chapter 6 explained that when a function is called using pass-by-value, a copy of the argument (or arguments) in the function call is made and passed to the function. If the copy is modified in the function, the original value is maintained in the caller without change. In many cases, a value passed to a function is modified so the function can accomplish its task. However, in some instances, the value should not be altered in the called function, even though the called function manipulates only a copy of the original value.

For example, consider a function that takes a one-dimensional array and its size as arguments and subsequently prints the array. Such a function should loop through the array and output each array element individually. The size of the array is used in the function body to determine the highest subscript of the array so the loop can terminate when the printing completes. The size of the array does not change in the function body, so it should be declared const. Of course, because the array is only being printed, it, too, should be declared const. This is especially important because an entire array is always passed by reference and could easily be changed in the called function.

Software Engineering Observation 8.2

If a value does not (or should not) change in the body of a function to which it is passed, the parameter should be declared const to ensure that it is not accidentally modified.

If an attempt is made to modify a const value, a warning or an error is issued, depending on the particular compiler.

Error-Prevention Tip 8.2

Before using a function, check its function prototype to determine the parameters that it can modify.

There are four ways to pass a pointer to a function: a nonconstant pointer to nonconstant data (Fig. 8.10), a nonconstant pointer to constant data (Fig. 8.11 and Fig. 8.12), a constant pointer to nonconstant data (Fig. 8.13) and a constant pointer to constant data (Fig. 8.14). Each combination provides a different level of access privileges.

Figure 8.10. Converting a string to uppercase.

(This item is displayed on page 413 in the print version)

1 // Fig. 8.10: fig08_10.cpp 2 // Converting lowercase letters to uppercase letters 3 // using a non-constant pointer to non-constant data. 4 #include 5 using std::cout; 6 using std::endl; 7 8 #include // prototypes for islower and toupper 9 using std::islower; 10 using std::toupper; 11 12 void convertToUppercase( char * ); 13 14 int main() 15 { 16 char phrase[] = "characters and $32.98"; 17 18 cout << "The phrase before conversion is: " << phrase; 19 convertToUppercase( phrase ); 20 cout << " The phrase after conversion is: " << phrase << endl; 21 return 0; // indicates successful termination 22 } // end main 23 24 // convert string to uppercase letters 25 void convertToUppercase( char *sPtr ) 26 { 27 while ( *sPtr != '' ) // loop while current character is not '' 28 { 29 if ( islower( *sPtr ) ) // if character is lowercase, 30 *sPtr = toupper( *sPtr ); // convert to uppercase 31 32 sPtr++; // move sPtr to next character in string 33 } // end while 34 } // end function convertToUppercase  

The phrase before conversion is: characters and $32.98 The phrase after conversion is: CHARACTERS AND $32.98  

Figure 8.11. Printing a string one character at a time using a nonconstant pointer to constant data.

(This item is displayed on page 414 in the print version)

1 // Fig. 8.11: fig08_11.cpp 2 // Printing a string one character at a time using 3 // a non-constant pointer to constant data. 4 #include 5 using std::cout; 6 using std::endl; 7 8 void printCharacters( const char * ); // print using pointer to const data 9 10 int main() 11 { 12 const char phrase[] = "print characters of a string"; 13 14 cout << "The string is: "; 15 printCharacters( phrase ); // print characters in phrase 16 cout << endl; 17 return 0; // indicates successful termination 18 } // end main 19 20 // sPtr can be modified, but it cannot modify the character to which 21 // it points, i.e., sPtr is a "read-only" pointer 22 void printCharacters( const char *sPtr ) 23 { 24 for ( ; *sPtr != ''; sPtr++ ) // no initialization 25 cout << *sPtr; // display character without modification 26 } // end function printCharacters  

The string is: print characters of a string  

Figure 8.12. Attempting to modify data through a nonconstant pointer to constant data.

(This item is displayed on page 415 in the print version)

1 // Fig. 8.12: fig08_12.cpp 2 // Attempting to modify data through a 3 // non-constant pointer to constant data. 4 5 void f( const int * ); // prototype 6 7 int main() 8 { 9 int y; 10 11 f( &y ); // f attempts illegal modification 12 return 0; // indicates successful termination 13 } // end main 14 15 // xPtr cannot modify the value of constant variable to which it points 16 void f( const int *xPtr ) 17 { 18 *xPtr = 100; // error: cannot modify a const object 19 } // end function f  

Borland C++ command-line compiler error message:

Error E2024 fig08_12.cpp 18: Cannot modify a const object in function f(const int *)  

 

Microsoft Visual C++ compiler error message:

c:cpphtp5_examplesch08Fig08_12fig08_12.cpp(18) : error C2166: l-value specifies const object  

 

GNU C++ compiler error message:

fig08_12.cpp: In function `void f(const int*)': fig08_12.cpp:18: error: assignment of read-only location  

Figure 8.13. Attempting to modify a constant pointer to nonconstant data.

(This item is displayed on page 416 in the print version)

1 // Fig. 8.13: fig08_13.cpp 2 // Attempting to modify a constant pointer to non-constant data. 3 4 int main() 5 { 6 int x, y; 7 8 // ptr is a constant pointer to an integer that can 9 // be modified through ptr, but ptr always points to the 10 // same memory location. 11 int * const ptr = &x; // const pointer must be initialized 12 13 *ptr = 7; // allowed: *ptr is not const 14 ptr = &y; // error: ptr is const; cannot assign to it a new address 15 return 0; // indicates successful termination 16 } // end main  

Borland C++ command-line compiler error message:

Error E2024 fig08_13.cpp 14: Cannot modify a const object in function main()s  

 

Microsoft Visual C++ compiler error message:

c:cpphtp5e_examplesch08Fig08_13fig08_13.cpp(14) : error C2166: l-value specifies const object  

 

GNU C++ compiler error message:

fig08_13.cpp: In function `int main()': fig08_13.cpp:14: error: assignment of read-only variable `ptr'  

Figure 8.14. Attempting to modify a constant pointer to constant data.

(This item is displayed on pages 417 - 418 in the print version)

1 // Fig. 8.14: fig08_14.cpp 2 // Attempting to modify a constant pointer to constant data. 3 #include 4 using std::cout; 5 using std::endl; 6 7 int main() 8 { 9 int x = 5, y; 10 11 // ptr is a constant pointer to a constant integer. 12 // ptr always points to the same location; the integer 13 // at that location cannot be modified. 14 const int *const ptr = &x; 15 16 cout << *ptr << endl; 17 18 *ptr = 7; // error: *ptr is const; cannot assign new value 19 ptr = &y; // error: ptr is const; cannot assign new address 20 return 0; // indicates successful termination 21 } // end main  

Borland C++ command-line compiler error message:

Error E2024 fig08_14.cpp 18: Cannot modify a const object in function main() Error E2024 fig08_14.cpp 19: Cannot modify a const object in function main()  

 

Microsoft Visual C++ compiler error message:

c:cpphtp5e_examplesch08Fig08_14fig08_14.cpp(18) : error C2166: l-value specifies const object c:cpphtp5e_examplesch08Fig08_14fig08_14.cpp(19) : error C2166: l-value specifies const object  

 

GNU C++ compiler error message:

fig08_14.cpp: In function `int main()': fig08_14.cpp:18: error: assignment of read-only location fig08_14.cpp:19: error: assignment of read-only variable `ptr'  

Nonconstant Pointer to Nonconstant Data

The highest access is granted by a nonconstant pointer to nonconstant datathe data can be modified through the dereferenced pointer, and the pointer can be modified to point to other data. The declaration for a nonconstant pointer to nonconstant data does not include const. Such a pointer can be used to receive a null-terminated string in a function that changes the pointer value to process (and possibly modify) each character in the string. Recall from Section 7.4 that a null-terminated string can be placed in a character array that contains the characters of the string and a null character indicating where the string ends.

In Fig. 8.10, function convertToUppercase (lines 2534) declares parameter sPtr (line 25) to be a nonconstant pointer to nonconstant data (again, const is not used). The function processes one character at a time from the null-terminated string stored in character array phrase (lines 2733). Keep in mind that a character array's name is really equivalent to a pointer to the first character of the array, so passing phrase as an argument to convertToUppercase is possible. Function islower (line 29) takes a character argument and returns true if the character is a lowercase letter and false otherwise. Characters in the range 'a' through 'z' are converted to their corresponding uppercase letters by function toupper (line 30); others remain unchangedfunction toupper takes one character as an argument. If the character is a lowercase letter, the corresponding uppercase letter is returned; otherwise, the original character is returned. Function toupper and function islower are part of the character-handling library (see Chapter 22, Bits, Characters, C-Strings and structs). After processing one character, line 32 increments sPtr by 1 (this would not be possible if sPtr were declared const). When operator ++ is applied to a pointer that points to an array, the memory address stored in the pointer is modified to point to the next element of the array (in this case, the next character in the string). Adding one to a pointer is one valid operation in pointer arithmetic, which is covered in detail in Section 8.8 and Section 8.9.



Nonconstant Pointer to Constant Data

A nonconstant pointer to constant data is a pointer that can be modified to point to any data item of the appropriate type, but the data to which it points cannot be modified through that pointer. Such a pointer might be used to receive an array argument to a function that will process each element of the array, but should not be allowed to modify the data. For example, function printCharacters (lines 2226 of Fig. 8.11) declares parameter sPtr (line 22) to be of type const char *, so that it can receive a null-terminated pointer-based string. The declaration is read from right to left as "sPtr is a pointer to a character constant." The body of the function uses a for statement (lines 2425) to output each character in the string until the null character is encountered. After each character is printed, pointer sPtr is incremented to point to the next character in the string (this works because the pointer is not const). Function main creates char array phrase to be passed to printCharacters. Again, we can pass the array phrase to printCharacters because the name of the array is really a pointer to the first character in the array.

Figure 8.12 demonstrates the compilation error messages produced when attempting to compile a function that receives a nonconstant pointer to constant data, then tries to use that pointer to modify the data. [Note: Remember that compiler error messages vary among compilers.]


As we know, arrays are aggregate data types that store related data items of the same type under one name. When a function is called with an array as an argument, the array is passed to the function by reference. However, objects are always passed by valuea copy of the entire object is passed. This requires the execution-time overhead of making a copy of each data item in the object and storing it on the function call stack. When an object must be passed to a function, we can use a pointer to constant data (or a reference to constant data) to get the performance of pass-by-reference and the protection of pass-by-value. When a pointer to an object is passed, only a copy of the address of the object must be made; the object itself is not copied. On a machine with four-byte addresses, a copy of four bytes of memory is made rather than a copy of a possibly large object.

Performance Tip 8.1

If they do not need to be modified by the called function, pass large objects using pointers to constant data or references to constant data, to obtain the performance benefits of pass-by-reference.


Software Engineering Observation 8.3

Pass large objects using pointers to constant data, or references to constant data, to obtain the security of pass-by-value.

 

Constant Pointer to Nonconstant Data

A constant pointer to nonconstant data is a pointer that always points to the same memory location; the data at that location can be modified through the pointer. This is the default for an array name. An array name is a constant pointer to the beginning of the array. All data in the array can be accessed and changed by using the array name and array subscripting. A constant pointer to nonconstant data can be used to receive an array as an argument to a function that accesses array elements using array subscript notation. Pointers that are declared const must be initialized when they are declared. (If the pointer is a function parameter, it is initialized with a pointer that is passed to the function.) The program of Fig. 8.13 attempts to modify a constant pointer. Line 11 declares pointer ptr to be of type int * const. The declaration in the figure is read from right to left as "ptr is a constant pointer to a nonconstant integer." The pointer is initialized with the address of integer variable x. Line 14 attempts to assign the address of y to ptr, but the compiler generates an error message. Note that no error occurs when line 13 assigns the value 7 to *ptrthe nonconstant value to which ptr points can be modified using the dereferenced ptr, even though ptr itself has been declared const.


Common Programming Error 8.6

Not initializing a pointer that is declared const is a compilation error.

 

Constant Pointer to Constant Data

The least amount of access privilege is granted by a constant pointer to constant data. Such a pointer always points to the same memory location, and the data at that memory location cannot be modified using the pointer. This is how an array should be passed to a function that only reads the array, using array subscript notation, and does not modify the array. The program of Fig. 8.14 declares pointer variable ptr to be of type const int * const (line 14). This declaration is read from right to left as "ptr is a constant pointer to an integer constant." The figure shows the error messages generated when an attempt is made to modify the data to which ptr points (line 18) and when an attempt is made to modify the address stored in the pointer variable (line 19). Note that no errors occur when the program attempts to dereference ptr, or when the program attempts to output the value to which ptr points (line 16), because neither the pointer nor the data it points to is being modified in this statement.


Категории