2.2.2 Pointer Variables and Dynamic Memory

As a programmer you may need to store a collection of records that are not all of the same type. In C they cannot be kept in a single array, since that would violate the array typing constraint, but there is a way out: simply declare each different type to correspond to one of the members of a union occurring in the record. With this trick, C treats records of different types as records of the same type. Another difficulty in C is how to store a collection of records of the same type when the maximum number of such records is not known in advance. Storing them in an array requires specifying the length of the array, but the programmer may not know what this should be. A practical solution to this problem is provided by the dynamic memory.

So far, by grouping records in arrays, it has been possible to refer to a record by its position in the array. This eliminates the need to attach a separate name to each record of the group. Dynamic memory may be viewed as a collection of storage elements that are on call for the storage of information. However, records stored in dynamic memory do not have individual names or array positions to be used for their reference. Instead, pointer variables are used to point to an individual record. A record in dynamic memory can be accessed only through its pointer variable. A pointer is like a string attached to a record: follow it and you get to the record. The pointers previously examined point to a record's position in an array, whereas pointer variables point to a record's location in dynamic memory. Both play the same role: they lead to a record.

Declaring a record type for records to be stored in dynamic memory is the same as for individual records or records stored in arrays. However, an associated variable of type pointer must be declared and used to refer to the record. For example, to store a record of type carrecord to be referenced by the pointer variable pcar 1 requires the following declaration:

struct carrecord *pcar1,car2;

An important distinction exists between the variable pcar1, which is a pointer, and car2 of type carrecord. The variable car2 is not stored in dynamic memory. Storage is allocated to car2 as soon as the component of the program in which it is declared is executed. The programmer need do nothing other than declare it to ensure this allocation of storage; statements assigning values to it may immediately be written.

This is not the case for the record pointed to by pcar1, which is to be stored in dynamic memory; pcar1 itself is not stored in dynamic memory, but the record it will point to is. This is because pcar1 was declared to be a pointer. The prefix, *, denotes this. No storage is allocated for the record that pcar1 will point to until a specific request made by the programmer is executed. This is done by an invocation of the C function malloc. Thus,

pcar1 = malloc(sizeof(struct carrecord));

when executed, places a pointer value into pcar1 and also allocates storage in dynamic memory for a record of type carrecord to which that pointer points. Until this is done, pcar1's value is undefined. After the function is executed, the situation may be pictured as in Figure 2.10.

The record may be referenced by pcar1 and its members referenced by pcar1->make, pcar1->rate, pcar1->mileage, and pcar1->rentee. At this point, though, the contents of the fields are undefined. Storage has simply been allocated for them in dynamic memory. They may be assigned values by using the assignment statement. Each field of the record pointed to by pcar1 can be given a value. Thus

pcar1->rate = 20.25;

assigns a value of 20.25 to the rate field of the record pointed to by pcar 1. You must be sure before assigning values to a record stored in dynamic memory that you have assigned a value to the pointer variable pointing to that record.

One advantage of pointer variables in C is that the value of a pointer can be printed. It means that a programmer can see the value of a pointer for debugging purposes. Other advantages of using pointer variables (that is, of storing records in dynamic memory) will soon become apparent.

Selection and traversal of a collection of records stored in dynamic memory may be accomplished using an array of pointers. The array entries are pointers that point to records in dynamic memory.

Example 2.5

The definition

struct carrecord *pcar[100];

establishes an array of 100 pointers that can address 100 records stored in dynamic memory. A reference to a member of a record is done by first specifying the pointer to the record, pcar[i], and then the desired member of the record, say make. The resultant reference would then be written as pcar[i]->make. This references the make member of the (i + 1)th record. n

Arrays of pointers to records in dynamic memory are not as easy to use as relative pointers to records in an array, but they provide significant flexibility. You shall see, though, that considerable care is needed to use them properly.

Figure 2.10 Dynamic Memory