9.2. Operations with PointersThis section describes the operations that can be performed using pointers. The most important of these operations is accessing the object or function that the pointer refers to. You can also compare pointers, and use them to iterate through a memory block. For a complete description of the individual operators in C, with their precedence and permissible operands, see Chapter 5. 9.2.1. Using Pointers to Read and Modify ObjectsThe indirection operator * yields the location in memory whose address is stored in a pointer. If ptr is a pointer, then *ptr designates the object (or function) that ptr points to. Using the indirection operator is sometimes called dereferencing a pointer. The type of the pointer determines the type of object that is assumed to be at that location in memory. For example, when you access a given location using an int pointer, you read or write an object of type int. Unlike the multiplication operator *, the indirection operator * is a unary operator; that is, it has only one operand. In Example 9-1, ptr points to the variable x. Hence the expression *ptr is equivalent to the variable x itself. Example 9-1. Dereferencing a pointerdouble x, y, *ptr; // Two double variables and a pointer to double. ptr = &x; // Let ptr point to x. *ptr = 7.8; // Assign the value 7.8 to the variable x. *ptr *= 2.5; // Multiply x by 2.5. y = *ptr + 0.5; // Assign y the result of the addition x + 0.5. Do not confuse the asterisk (*) in a pointer declaration with the indirection operator. The syntax of the declaration can be seen as an illustration of how to use the pointer. An example: double *ptr; As declared here, ptr has the type double * (read: "pointer to double"). Hence the expression *ptr would have the type double. Of course, the indirection operator * must be used with only a pointer that contains a valid address. This usage requires careful programming! Without the assignment ptr = &x in Example 9-1, all of the statements containing *ptr would be senselessdereferencing an undefined pointer valueand might well cause the program to crash. A pointer variable is itself an object in memory, which means that a pointer can point to it. To declare a pointer to a pointer , you must use two asterisks, as in the following example: char c = 'A', *cPtr = &c, **cPtrPtr = &cPtr; The expression *cPtrPtr now yields the char pointer cPtr, and the value of **cPtrPtr is the char variable c. The diagram in Figure 9-2 illustrates these references. Figure 9-2. A pointer to a pointerPointers to pointers are not restricted to the two-stage indirection illustrated here. You can define pointers with as many levels of indirection as you need. However, you cannot assign a pointer to a pointer its value by mere repetitive application of the address operator: char c = 'A', **cPtrPtr = &(&c); // Wrong! The second initialization in this example is illegal: the expression (&c) cannot be the operand of &, because it is not an lvalue. In other words, there is no pointer to char in this example for cPtrPtr to point to. If you pass a pointer to a function by reference so that the function can modify its value, then the function's parameter is a pointer to a pointer. The following simple example is a function that dynamically creates a new record and stores its address in a pointer variable: #include <stdlib.h> // The record type: typedef struct { long key; /* ... */ } Record; _Bool newRecord( Record **ppRecord ) { *ppRecord = malloc( sizeof(Record) ); if ( *ppRecord != NULL ) { /* ... Initialize the new record's members ... */ return 1; } else return 0; } The following statement is one possible way to call the newRecord( ) function: Record *pRecord = NULL; if ( newRecord( &pRecord) ) { /* ... pRecord now points to a new Record object ... */ } The expression *pRecord yields the new record, and (*pRecord).key is the member key in that record. The parentheses in the expression (*pRecord).key are necessary, because the dot operator (.) has higher precedence than the indirection operator (*). Instead of this combination of operators and parentheses, you can also use the arrow operator -> to access structure or union members. If p is a pointer to a structure or union with a member m, then the expression p->m is equivalent to (*p).m. Thus the following statement assigns a value to the member key in the structure that pRecord points to: pRecord->key = 123456L; 9.2.2. Modifying and Comparing PointersBesides using assignments to make a pointer refer to a given object or function, you can also modify an object pointer using arithmetic operations. When you perform pointer arithmetic, the compiler automatically adapts the operation to the size of the objects referred to by the pointer type. You can perform the following operations on pointers to objects:
When you subtract one pointer from another, the two pointers must have the same basic type, although you can disregard any type qualifiers (see "Implicit Pointer Conversions" in Chapter 4). Furthermore, you may compare any pointer with a null pointer constant using the equality operators (== and !=), and you may compare any object pointer with a pointer to void. The three pointer operations described here are generally useful only for pointers that refer to the elements of an array. To illustrate the effects of these operations, consider two pointers p1 and p2, which point to elements of an array a:
Because the name of an array is implicitly converted into a pointer to the first array element wherever necessary, you can also substitute pointer arithmetic for array subscript notation:
In Example 9-2, the function selection_sortf( ) sorts an array of float elements using the selection-sort algorithm. This is the pointer version of the function selection_sortf( ) in Example 7-7; in other words, this function does the same job, but uses pointers instead of indices. The helper function swapf( ) remains unchanged. Example 9-2. Pointer version of the selection_sortf( ) function// The swapf( ) function exchanges the values of two float variables. // Arguments: Two pointers to float. inline void swapf( float *p1, float *p2 ); { float tmp = *p1; *p1 = *p2; *p2 = tmp; // Swap *p1 and *p2. } // The function selection_sortf( ) uses the selection-sort // algorithm to sort an array of float elements. // Arguments: An array of float, and its length. void selection_sortf( float a[ ], int n ) // Sort an array a of n float elements. { if ( n <= 1 ) return; // Nothing to sort. register float *last = a + n-1, // A pointer to the last element. *p, // A pointer to a selected element. *minPtr; // A pointer to the current minimum. for ( ; a < last; ++a ) // Walk the pointer a through the array. { minPtr = a; // Find the smallest element for ( p = a+1; p <= last; ++p ) // between a and the end of the array. if ( *p < *minPtr ) minPtr = p; swapf( a, minPtr ); // Swap the smallest element } // with the element at a. } The pointer version of such a function is generally more efficient than the index version, since accessing the elements of the array a using an index i, as in the expression a[i] or *(a+i), involves adding the address a to the value i*sizeof(element_type) to obtain the address of the corresponding array element. The pointer version requires less arithmetic, because the pointer itself is incremented instead of the index, and points to the required array element directly. |