Previous Page
Next Page

9.1. Declaring Pointers

A pointer represents both the address and the type of an object or function. If an object or function has the type T, then a pointer to it has the derived type pointer to T. For example, if var is a float variable, then the expression &varwhose value is the address of the float variablehas the type pointer to float, or in C notation, the type float *. A pointer to any type T is also called a T pointer for short. Thus the address operator in &var yields a float pointer.

Because var doesn't move around in memory, the expression &var is a constant pointer. However, C also allows you to define variables with pointer types. A pointer variable stores the address of another object or a function. We describe pointers to arrays and functions a little further on. To start out, the declaration of a pointer to an object that is not an array has the following syntax:

type * [type-qualifier-list] name [= initializer];

In declarations, the asterisk (*) means "pointer to." The identifier name is declared as an object with the type type *, or pointer to type. The optional type qualifier list may contain any combination of the type qualifiers const, volatile, and restrict. For details about qualified pointer types, see the section "Pointers and Type Qualifiers," later in this chapter.

Here is a simple example:

int *iPtr;          // Declare iPtr as a pointer to int.

The type int is the type of object that the pointer iPtr can point to. To make a pointer refer to a certain object, assign it the address of the object. For example, if iVar is an int variable, then the following assignment makes iPtr point to the variable iVar:

iPtr = &iVar;       // Let iPtr point to the variable iVar.

The general form of a declaration consists of a comma-separated list of declarators, each of which declares one identifier (see Chapter 11). In a pointer declaration, the asterisk (*) is part of an individual declarator. We can thus define and initialize the variables iVar and iPtr in one declaration, as follows:

int iVar = 77, *iPtr = &iVar; // Define an int variable and a pointer to it.

The second of these two declarations initializes the pointer iPtr with the address of the variable iVar, so that iPtr points to iVar.

Figure 9-1 illustrates one possible arrangement of the variables iVar and iPtr in memory. The addresses shown are purely fictitious examples. As Figure 9-1 shows, the value stored in the pointer iPtr is the address of the object iVar.

Figure 9-1. A pointer and another object in memory

It is often useful to output addresses for verification and debugging purposes. The printf( ) functions provide a format specifier for pointers: %p. The following statement prints the address and contents of the variable iPtr:

printf( "Value of iPtr (i.e. the address of iVar): %p\n"
        "Address of iPtr:                          %p\n", iPtr, &iPtr );

The size of a pointer in memorygiven by the expression sizeof(iPtr), for exampleis the same regardless of the type of object addressed. In other words, a char pointer takes up just as much space in memory as a pointer to a large structure. On 32-bit computers, pointers are usually four bytes long.

9.1.1. Null Pointers

A null pointer is what results when you convert a null pointer constant to a pointer type. A null pointer constant is an integer constant expression with the value 0, or such an expression cast as the type void * (see "Null Pointer Constants" in Chapter 4). The macro NULL is defined in stdlib.h, stdio.h and other header files as a null pointer constant.

A null pointer is always unequal to any valid pointer to an object or function. For this reason, functions that return a pointer type usually use a null pointer to indicate a failure condition. One example is the standard function fopen( ), which returns a null pointer if it fails to open a file in the specified mode:

#include <stdio.h>
/* ... */
FILE *fp = fopen( "demo.txt", "r" );
if ( fp == NULL )
  // Error: unable to open the file demo.txt for reading.

Null pointers are implicitly converted to other pointer types as necessary for assignment operations, or for comparisons using == or !=. Hence no cast operator is necessary in the previous example. (See also "Implicit Pointer Conversions" in Chapter 4.)

9.1.2. void Pointers

A pointer to void, or void pointer for short, is a pointer with the type void *. As there are no objects with the type void, the type void * is used as the all-purpose pointer type. In other words, a void pointer can represent the address of any objectbut not its type. To access an object in memory, you must always convert a void pointer into an appropriate object pointer.

To declare a function that can be called with different types of pointer arguments, you can declare the appropriate parameters as pointers to void. When you call such a function, the compiler implicitly converts an object pointer argument into a void pointer. A common example is the standard function memset( ), which is declared in the header file string.h with the following prototype:

void *memset( void *s, int c, size_t n );

The function memset( ) assigns the value of c to each of the n bytes of memory in the block beginning at the address s. For example, the following function call assigns the value 0 to each byte in the structure variable record:

struct Data { /* ... */ } record;
memset( &record, 0, sizeof(record) );

The argument &record has the type struct Data *. In the function call, the argument is converted to the parameter's type, void *.

The compiler likewise converts void pointers into object pointers where necessary. For example, in the following statement, the malloc( ) function returns a void pointer whose value is the address of the allocated memory block. The assignment operation converts the void pointer into a pointer to int:

int *iPtr = malloc( 1000 * sizeof(int) );

For a more thorough illustration, see Example 2-3.

9.1.3. Initializing Pointers

Pointer variables with automatic storage duration start with an undefined value, unless their declaration contains an explicit initializer. All variables defined within any block, without the storage class specifier static, have automatic storage duration. All other pointers defined without an initializer have the initial value of a null pointer.

You can initialize a pointer with the following kinds of initializers:

  • A null pointer constant.

  • A pointer to the same type, or to a less qualified version of the same type (see the section "Pointers and Type Qualifiers," later in this chapter).

  • A void pointer, if the pointer being initialized is not a function pointer. Here again, the pointer being initialized can be a pointer to a more qualified type.

Pointers that do not have automatic storage duration must be initialized with a constant expression, such as the result of an address operation or the name of an array or function.

When you initialize a pointer, no implicit type conversion takes place except in the cases just listed. However, you can explicitly convert a pointer value to another pointer type. For example, to read any object byte by byte, you can convert its address into a char pointer to the first byte of the object:

double x = 1.5;
char *cPtr = &x;          // Error: type mismatch; no implicit conversion.
char *cPtr = (char *)&x;  // OK: cPtr points to the first byte of x.

For more details and examples of pointer type conversions, see the section "Explicit Pointer Conversions" in Chapter 4.

Previous Page
Next Page