Mastering Algorithms With C - Kyle Loudon [13]
Array Reference
Pointer Reference
int f1(int a[]) {
a[0] = 5;
return 0;
}
int f2(int *a) {
*a = 5;
return 0;
}
Usually the approach chosen depends on a convention or on wanting to convey something about how the parameter is used in the function. When using an array parameter, bounds information is often omitted since it is not required by the compiler. However, including bounds information can be a useful way to document a limit the function imposes on a parameter internally. Bounds information plays a more critical role with array parameters that are multidimensional.
When defining a function that accepts a multidimensional array, all but the first dimension must be specified so that pointer arithmetic can be performed when elements are accessed, as shown in the following code:
int g(int a[][2]) {
a[2][0] = 5;
return 0;
}
To understand why we must include all but the first dimension, imagine a two-dimensional array of integers with three rows and two columns. In C, elements are stored in row-major order at increasing addresses in memory. This means that the two integers in the first row are stored first, followed by the two integers in the second row, followed by the two integers of the third row. Therefore, to access an element in any row but the first, we must know exactly how many elements to skip in each row to get to elements in successive rows (see Figure 2.6).
Figure 2.6. Writing 5 to row 2, column 0, in a 2 × 3 array of integers (a) conceptually and (b) as viewed in memory
Pointers to Pointers as Parameters
One situation in which pointers are used as parameters to functions a great deal in this book is when a function must modify a pointer passed into it. To do this, the function is passed a pointer to the pointer to be modified. Consider the operation list_rem_next, which Chapter 5 defines for removing an element from a linked list. Upon return, data points to the data removed from the list:
int list_rem_next(List *list, ListElmt *element, void **data);
Since the operation must modify the pointer data to make it point to the data removed, we must pass the address of the pointer data in order to simulate call-by-reference parameter passing (see Figure 2.7). Thus, the operation takes a pointer to a pointer as its third parameter. This is typical of how data is removed from most of the data structures presented in this book.
Figure 2.7. Using a function to modify a pointer to point to an integer removed from a linked list
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 #include int swap2(void *x, void *y, int size) { void *tmp; if