Generic Pointers and Casts

Recall that pointer variables in C have types just like other variables. The main reason for this is so that when we dereference a pointer, the compiler knows the type of data being pointed to and can access the data accordingly. However, sometimes we are not concerned about the type of data a pointer references. In these cases we use generic pointers, which bypass C’s type system.

Generic Pointers

Normally C allows assignments only between pointers of the same type. For example, given a character pointer sptr (a string) and an integer pointer iptr, we are not permitted to assign sptr to iptr or iptr to sptr. However, generic pointers can be set to pointers of any type, and vice versa. Thus, given a generic pointer gptr, we are permitted to assign sptr to gptr or gptr to sptr. To make a pointer generic in C, we declare it as a void pointer .

There are many situations in which void pointers are useful. For example, consider the standard C library function memcpy, which copies a block of data from one location in memory to another. Because memcpy may be used to copy data of any type, it makes sense that its pointer parameters are void pointers. Void pointers can be used to make other types of functions more generic as well. For example, we might have implemented the swap2 function presented earlier so that it swapped data of any type, as shown in the following code:

#include <stdlib.h>
#include <string.h>

int swap2(void *x, void *y, int size) {

void *tmp;

if ((tmp = malloc(size)) == NULL)
   return -1;

memcpy(tmp, x, size); memcpy(x, y, size); memcpy(y, tmp, size);
free(tmp);

return 0;

}

Void pointers are particularly useful when implementing data structures because they allow us to store and retrieve data of any type. Consider again the ListElmt structure presented earlier for linked lists. Recall that this structure contains two members, data and next. Since data is declared as a void pointer, it can point to data of any type. Thus, we can use ListElmt structures to build any type of list.

In Chapter 5, one of the operations defined for linked lists is list_ins_next, which accepts a void pointer to the data to be inserted:

int list_ins_next(List *list, ListElmt *element, void *data);

To insert an integer referenced by iptr into a list of integers, list, after an element referenced by element, we use the following call. C permits us to pass the integer pointer iptr for the parameter data because data is a void pointer.

retval = list_ins_next(&list, element, iptr);

Of course, when removing data from the list, it is important to use the correct type of pointer to retrieve the data removed. Doing so ensures that the data will be interpreted correctly if we try to do something with it. As discussed earlier, the operation for removing an element from a linked list is list_rem_next (see Chapter 5), which takes a pointer to a void pointer as its third parameter:

int list_rem_next(List *list, ListElmt *element, void **data);

To remove an integer from list after an element referenced by element, we use the following call. Upon return, iptr points to the data removed. We pass the address of the pointer iptr since the operation modifies the pointer itself to make it point to the data removed.

retval = list_rem_next(&list, element, (void **)&iptr);

This call also includes a cast to make iptr temporarily appear as a pointer to a void pointer, since this is what list_rem_next requires. As we will see in the next section, casting is a mechanism in C that lets us temporarily treat a variable of one type as a variable of another type. A cast is necessary here because, although a void pointer is compatible with any other type of pointer in C, a pointer to a void pointer is not.

Casts

To cast a variable t of some type T to another type S, we precede t with S in parentheses. For example, to assign an integer pointer iptr to a floating-point pointer fptr, we cast iptr to a floating-point pointer and then carry out the assignment, as shown:

fptr = (float *)iptr;

(Although casting an integer pointer to a floating-point pointer is a dangerous practice in general, it is presented here as an illustration.) After the assignment, iptr and fptr both contain the same address. However, the interpretation of the data at this address depends on which pointer we use to access it.

Casts are especially important with generic pointers because generic pointers cannot be dereferenced without casting them to some other type. This is because generic pointers give the compiler no information about what is being pointed to; thus, it is not clear how many bytes should be accessed, nor how the bytes should be interpreted. Casts are also a nice form of self-documentation when generic pointers are assigned to pointers of other types. Although the cast is not necessary in this case, it does improve a program’s readability.

When casting pointers, one issue we need to be particularly sensitive to is the way data is aligned in memory. Specifically, we need to be aware that applying casts to pointers can undermine the alignment a computer expects. Often computers have alignment requirements so that certain hardware optimizations can make accessing memory more efficient. For example, a system may insist that all integers be aligned on word boundaries. Thus, given a void pointer that is not word aligned, if we cast the void pointer to an integer pointer and dereference it, we can expect an exception to occur at runtime.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.94.153