Previous Page
Next Page

9.3. Pointers and Type Qualifiers

The declaration of a pointer may contain the type qualifiers const, volatile, and/or restrict. The type qualifiers const and volatile may qualify either the pointer type itself, or the type of object it points to. The difference is important. Those type qualifiers that occur in the pointer's declaratorthat is, between the asterisk and the pointer's namequalify the pointer itself. An example:

short const volatile * restrict ptr;

In this declaration, the keyword restrict qualifies the pointer ptr. This pointer can refer to objects of type short that may be qualified with const or volatile, or both.

An object whose type is qualified with const is constant: the program cannot modify it after its definition. The type qualifier volatile is a hint to the compiler that the object so qualified may be modified not only by the present program, but also by other processes or events (see Chapter 11).

The most common use of qualifiers in pointer declarations is in pointers to constant objects, especially as function parameters. For this reason, the following description refers to the type qualifier const. The same rules govern the use of the type qualifier volatile with pointers.

9.3.1. Constant Pointers and Pointers to Constant Objects

When you define a constant pointer, you must also initialize it, because you can't modify it later. As the following example illustrates, a constant pointer does not necessarily point to a constant object:

int var;                 // An object with type int.
int *const c_ptr = &var; // A constant pointer to int.
*c_ptr = 123;            // OK: we can modify the object referenced, but ...
++c_ptr;                 // error: we can't modify the pointer.

You can modify a pointer that points to an object that has a const-qualified type (also called a pointer to const). However, you can use such a pointer only to read the referenced object, not to modify it. For this reason, pointers to const are commonly called "read-only pointers ." The referenced object itself may or may not be constant. An example:

int var;                     // An object with type int.
const int c_var = 100,       // A constant int object.
          *ptr_to_const;     // A pointer to const 
                             // the pointer itself is not constant!
ptr_to_const = &c_var;       // OK: Let ptr_to_const point to c_var.
var = 2 * *ptr_to_const;     // OK. Equivalent to: var = 2 * c_var;
ptr_to_const = &var;         // OK: Let ptr_to_const point to var.
if ( c_var < *ptr_to_const ) // OK: "read-only" access.
  *ptr_to_const = 77;        // Error: we can't modify var using
                             // ptr_to_const, even though var is
                             // not constant.

Type specifiers and type qualifiers can be written in any order. Thus the following is permissible:

int const c_var = 100, *ptr_to_const;

The assignment ptr_to_const = &var entails an implicit conversion: the int pointer value &var is automatically converted to the left operand's type, pointer to const int. For any operator that requires operands with like types, the compiler implicitly converts a pointer to a given type T into a more qualified version of the type T. If you want to convert a pointer into a pointer to a less-qualified type, you must use an explicit type conversion. The following code fragment uses the variables declared in the previous example:

int *ptr = &var;       // An int pointer that points to var.
*ptr = 77;             // OK: ptr is not a read-only pointer.
ptr_to_const = ptr;    // OK: implicitly converts ptr from "pointer to int"
                       // into "pointer to const int".
*ptr_to_const = 77;    // Error: can't modify a variable through a read-only
                       // pointer.
ptr = &c_var;          // Error: can't implicitly convert "pointer to const
                       // int" into "pointer to int".
ptr = (int *)&c_var;   // OK: Explicit pointer conversions are always
                       // possible.
*ptr = 200;            // Attempt to modify c_var: possible runtime error.

The final statement causes a runtime error if the compiler has placed the constant object c_var in a read-only section in memory.

You can also declare a constant pointer to const, as the parameter declaration in the following function prototype illustrates:

void func( const int * const c_ptr_to_const );

The function's parameter is a read-only pointer that is initialized when the function is called and remains constant within the function.

9.3.2. Restricted Pointers

C99 introduced the type qualifier restrict , which is applicable only to object pointers. A pointer qualified with restrict is called a restricted pointer. There is a special relationship between a restrict-qualified pointer and the object it points to: during the lifetime of the pointer, either the object is not modified, or the object is not accessed except through the restrict-qualified pointer. An example:

typedef struct { long key;                       // Define a structure type.
                 /* ... other members ... */
               } Data_t;
Data_t * restrict rPtr = malloc( sizeof(Data_t) ); // Allocate a structure.

This example illustrates one way to respect the relationship between the restricted pointer and its object: the return value of malloc( )the address of an anonymous Data_t objectis assigned only to the pointer rPtr, so the program won't access the object in any other way.

It is up to you, the programmer, to make sure that an object referenced by a restrict-qualified pointer is accessed only through that pointer. For example, if your program modifies an object through a restricted pointer, it must not access the object by name or through another pointer for as long as the restricted pointer exists.

The restrict type qualifier is a hint to the compiler that allows it to apply certain optimization techniques that might otherwise introduce inconsistencies. However, the restrict qualifier does not mandate any such optimization, and the compiler may ignore it. The program's outward behavior is the same in either case.

The type qualifier restrict is used in the prototypes of many standard library functions. For example, the function memcpy( ) is declared in the header file string.h as follows:

void *memcpy( void * restrict dest,         // Destination
              const void * restrict src,    // Source
              size_t n );                   // Number of bytes to copy

This function copies a memory block of n bytes, beginning at the address src, to the location beginning at dest. Because the pointer parameters are both restricted, you must make sure that the function will not use them to access the same objects: in other words, make sure that the source and destination blocks do not overlap. The following example contains one correct and one incorrect memcpy( ) call:

int a[200];
/* ... */
memcpy( a+100, a, 100 );  // OK: copy the first half of the array
                          // to the the second half; no overlap.
memcpy( a+1, a, 199 );    // Error: move the whole array contents upward by
                          // one index; large overlap.

The second memcpy( ) call in this example violates the restrict condition, because the function must modify 198 locations that it accesses using both pointers.

The standard function memmove( ), unlike memcpy( ), allows the source and destination blocks to overlap. Accordingly, neither of its pointer parameters has the restrict qualifier:

void *memmove( void *dest, const void *src, size_t n );

Example 9-3 illustrates the second way to fulfill the restrict condition: the program may access the object pointed to using other names or pointers, if it doesn't modify the object for as long as the restricted pointer exists. This simple function calculates the scalar product of two arrays.

Example 9-3. The function scalar_product( )
// This function calculates the scalar product of two arrays.
// Arguments: Two arrays of double, and their length.
//            The two arrays need not be distinct.

double scalar_product( const double * restrict p1,
                       const double * restrict p2,
                       int n )
  double result = 0.0;
  for ( int i = 0; i < n; ++i )
    result += p1[i] * p2[i];
  return result;

Assuming an array named P with three double elements, you could call this function using the expression scalar_products( P, P, 3 ). The function accesses objects through two different restricted pointers, but as the const keyword in the first two parameter declarations indicates, it doesn't modify them.

Previous Page
Next Page