Вы находитесь на странице: 1из 19

Chapter 22: Pointers to Pointers

Since we can have pointers to int, and pointers to char, and pointers to any structures we've defined, and in fact pointers to any type in C, it shouldn't come as too much of a surprise that we can have pointers to other pointers. If we're used to thinking about simple pointers, and to keeping clear in our minds the distinction between the pointer itself and what it points to, we should be able to think about pointers to pointers, too, although we'll now have to distinguish between the pointer, what it points to, and what the pointer that it points to points to. (And, of course, we might also end up with pointers to pointers to pointers, or pointers to pointers to pointers to pointers, although these rapidly become too esoteric to have any practical use.) The declaration of a pointer-to-pointer looks like
int **ipp;

where the two asterisks indicate that two levels of pointers are involved. Starting off with the familiar, uninspiring, kindergarten-style examples, we can demonstrate the use of ipp by declaring some pointers for it to point to and some ints for those pointers to point to:
int i = 5, j = 6; k = 7; int *ip1 = &i, *ip2 = &j;

Now we can set

ipp = &ip1;

and ipp points to ip1 which points to i. *ipp is ip1, and **ipp is i, or 5. We can illustrate the situation, with our familiar box-and-arrow notation, like this:

If we say

*ipp = ip2;

we've changed the pointer pointed to by ipp (that is, ip1) to contain a copy of ip2, so that it (ip1) now points at j:

If we say

*ipp = &k;

we've changed the pointer pointed to by ipp (that is, ip1 again) to point to k:

What are pointers to pointers good for, in practice? One use is returning pointers from functions, via pointer arguments rather than as the formal return value. To explain this, let's first step back and consider the case of returning a simple type, such as int, from a function via a pointer argument. If we write the function
f(int *ip) { *ip = 5; }

and then call it like this:


int i; f(&i);

then f will ``return'' the value 5 by writing it to the location specified by the pointer passed by the caller; in this case, to the caller's variable i. A function might ``return'' values in this way if it had multiple things to return, since a function can only have one formal return value (that is, it can only return one value via the return statement.) The important thing to notice is that for the function to return a value of type int, it used a parameter of type pointer-to-int. Now, suppose that a function wants to return a pointer in this way. The corresponding parameter will then have to be a pointer to a pointer. For example, here is a little function which tries to allocate memory for a string of length n, and which returns zero (``false'') if it fails and 1 (nonzero, or ``true'') if it succeeds, returning the actual pointer to the allocated memory via a pointer:
#include <stdlib.h> int allocstr(int len, char **retptr) { char *p = malloc(len + 1); if(p == NULL) return 0; *retptr = p; return 1; }

/* +1 for \0 */

The caller can then do something like

char *string = "Hello, world!"; char *copystr; if(allocstr(strlen(string), &copystr)) strcpy(copystr, string); else fprintf(stderr, "out of memory\n"); (This is a fairly crude example; the allocstr function is not terribly useful. It would have been just about as easy for the caller to call malloc directly. A different, and more useful, approach to writing a ``wrapper'' function around malloc is exemplified by the chkmalloc function we've

been using.)

One side point about pointers to pointers and memory allocation: although the void * type, as returned by malloc, is a ``generic pointer,'' suitable for assigning to or from pointers of any type, the hypothetical type void ** is not a ``generic pointer to pointer.'' Our allocstr example can only be used for allocating pointers to char. It would not be possible to use a function which returned generic pointers indirectly via a void ** pointer, because when you tried to use it, for example by declaring and calling
double *dptr; if(!hypotheticalwrapperfunc(100, sizeof(double), &dptr)) fprintf(stderr, "out of memory\n"); would not be passing a void **, but rather a double **.

you

Another good use for pointers to pointers is in dynamically allocated, simulated multidimensional arrays, which we'll discuss in the next chapter. As a final example, let's look at how pointers to pointers can be used to eliminate a nuisance we've had when trying to insert and delete items in linked lists. For simplicity, we'll consider lists of integers, built using this structure:
struct list { int item; struct list *next; };

Suppose we're trying to write some code to delete a given integer from a list. The straightforward solution looks like this:
/* delete node containing i from list pointed to by lp */ struct list *lp, *prevlp; for(lp = list; lp != NULL; lp = lp->next) { if(lp->item == i) { if(lp == list) list = lp->next; else prevlp->next = lp->next; break; } prevlp = lp; } }

This code works, but it has two blemishes. One is that it has to use an extra variable to keep track of the node one behind the one it's looking at, and the other is that it has to use an extra test to special-case the situation in which the node being deleted is at the head of the list. Both of these problems arise because the deletion of a node from the list involves modifying the previous pointer to point to the next node (that is, the node before the deleted node to point to the one following). But, depending on whether the node being deleted is the first node in the list or not, the pointer that needs modifying is either the pointer that points to the head of the list, or the next pointer in the previous node. To illustrate this, suppose that we have the list (1, 2, 3) and we're trying to delete the element 1. After we've found the element 1, lp points to its node, which just happens to be the same node that the main list pointer points to, as illustrated in (a) below:

To remove element 1 from the list, then, we must adjust the main list pointer so that it points to 2's node, the new head of the list (as shown in (b)). If we were trying to delete node 2, on the other hand (as illustrated in (c) above), we'd have to adjust node 1's next pointer to point to 3. The prevlp pointer keeps track of the previous node we were looking at, since (at other than the first node in the list) that's the node whose next pointer will need adjusting. (Notice that if we were to delete node 3, we would copy its next pointer over to 2, but since 3's next pointer is the null pointer, copying it to node 2 would make node 2 the end of the list, as desired.) We can write another version of the list-deletion code, which is (in some ways, at least) much cleaner, by using a pointer to a pointer to a struct list. This pointer will point at the pointer which points at the node we're looking at; it will either point at the head pointer or at the next pointer of the node we looked at last time. Since this pointer points at the pointer that points at the node we're looking at (got that?), it points at the pointer which we need to modify if the node we're looking at is the node we're deleting. Let's see how the code looks:
struct list **lpp; for(lpp = &list; *lpp != NULL; lpp = &(*lpp)->next) { if((*lpp)->item == i) { *lpp = (*lpp)->next; break; } } }

That single line

*lpp = (*lpp)->next;

updates the correct pointer, to splice the node it refers to out of the list, regardless of whether the pointer being updated is the head pointer or one of the next pointers. (Of course, the payoff is not absolute, because the use of a pointer to a pointer to a struct list leads to an algorithm which might not be nearly as obvious at first glance.) To illustrate the use of the pointer-to-pointer lpp graphically, here are two more figures illustrating the situation just before deleting node 1 (on the left) or node 2 (on the right).

In both cases, lpp points at a struct node pointer which points at the node to be deleted. In both cases, the pointer pointed to by lpp (that is, the pointer *lpp) is the pointer that needs to be updated. In both cases, the new pointer (the pointer that *lpp is to be updated to) is the next pointer of the node being deleted, which is always (*lpp)->next. One other aspect of the code deserves mention. The expression
(*lpp)->next describes the next pointer

of the struct node which is pointed to by *lpp, that is, which is pointed to by the pointer which is pointed to by lpp. The expression
lpp = &(*lpp)->next sets lpp to point to the next field of parentheses around *lpp are needed

the struct list pointed to by *lpp. In both cases, the because the precedence of * is lower than ->.

As a second, related example, here is a piece of code for inserting a new node into a list, in its proper order. This code uses a pointer-to-pointer-to-struct list for the same reason, namely, so that it doesn't have to worry about treating the beginning of the list specially.
/* insert node newlp into list */ struct list **lpp; for(lpp = &list; *lpp != NULL; lpp = &(*lpp)->next) { struct list *lp = *lpp; if(newlp->item < lp->item) { newlp->next = lp; *lpp = newlp; break; } } }

23.1: Multidimensional Arrays and Functions


The most straightforward way of passing a multidimensional array to a function is to declare it in exactly the same way in the function as it was declared in the caller. If we were to call
func(a2);

then we might declare


func(int a[5][7]) { ... }

and it's clear that the array type which the caller passes is the same as the type which the function func accepts. If we remember what we learned about simple arrays and functions, however, two questions arise. First, in our earlier function definitions, we were able to leave out the (single) array dimension, with the understanding that since the array was really defined in the caller, we didn't have to say (or know) how big it is. The situation is the same for multidimensional arrays, although it may not seem so at first. The hypothetical function func above accepts a parameter a, where a is an array of 5 things, where each of the 5 things is itself an array. By the same argument that applies in the single-dimension case, the function does not have to know how big the array a is, overall. However, it certainly does need to know what a is an array of. It is not enough to know that a is an array of ``other arrays''; the function must know that a is an array of arrays of 7 ints. The upshot is that although it does not need to know how many ``rows'' the array has, it does need to know the number of columns. That is, if we want to leave out any dimensions, we can only leave out the first one:
func(int a[][7]) { ... }

The second dimension is still required. (For a three- or more dimensional array, all but the first dimension are required; again, only the first dimension may be omitted.) The second question we might ask concerns the equivalence between pointers and arrays. We know that when we pass an array to a function, what really gets passed is a pointer to the array's first element. We know that when we declare a function that seems to accept an array as a parameter, the compiler quietly compiles the function as if that parameter were a pointer, since a pointer is what it will actually receive. What about multidimensional arrays? What kind of pointer is passed down to the function? The answer is, a pointer to the array's first element. And, since the first element of a multidimensional array is another array, what gets passed to the function is a pointer to an array. If you want to declare the function func in a way that explicitly shows the type which it receives, the declaration would be
func(int (*a)[7]) { ... } The declaration int (*a)[7] says that a is a pointer to an array of 7 ints. Since declarations like this

are hard to write and hard to understand, and since pointers to arrays are generally confusing, I recommend that when you write functions which accept multidimensional arrays, you declare the parameters using array notation, not pointer notation. What if you don't know what the dimensions of the array will be? What if you want to be able to call a function with arrays of different sizes and shapes? Can you say something like
func(int x, int y, int a[x][y]) { ...

where the array dimensions are specified by other parameters to the function? Unfortunately, in C, you cannot. (You can do so in FORTRAN, and you can do so in the extended language implemented by gcc, and you will be able to do so in the new version of the C Standard (``C9X'') to be completed in 1999, but you cannot do so in standard, portable C, today.) Finally, we might explicitly note that if we pass a multidimensional array to a function:
int a2[5][7]; func(a2);

we can not declare that function as accepting a pointer-to-pointer:


func(int **a) { ... } /* WRONG */

As we said above, the function ends up receiving a pointer to an array, not a pointer to a pointer.

23.2: Dynamically Allocating Multidimensional Arrays


We've seen that it's straightforward to call malloc to allocate a block of memory which can simulate an array, but with a size which we get to pick at run-time. Can we do the same sort of thing to simulate multidimensional arrays? We can, but we'll end up using pointers to pointers. If we don't know how many columns the array will have, we'll clearly allocate memory for each row (as many columns wide as we like) by calling malloc, and each row will therefore be represented by a pointer. How will we keep track of those pointers? There are, after all, many of them, one for each row. So we want to simulate an array of pointers, but we don't know how many rows there will be, either, so we'll have to simulate that array (of pointers) with another pointer, and this will be a pointer to a pointer. This is best illustrated with an example:
#include <stdlib.h> int **array; array = malloc(nrows * sizeof(int *)); if(array == NULL) { fprintf(stderr, "out of memory\n"); exit or return

} for(i = 0; i < nrows; i++) { array[i] = malloc(ncolumns * sizeof(int)); if(array[i] == NULL) { fprintf(stderr, "out of memory\n"); exit or return } } array is a pointer-to-pointer-to-int: at the first level, it points to a block of pointers, one for each row.

That first-level pointer is the first one we allocate; it has nrows elements, with each element big enough to hold a pointer-to-int, or int *. If we successfully allocate it, we then fill in the pointers (all nrows of them) with a pointer (also obtained from malloc) to ncolumns number of ints, the storage for that row of the array. If this isn't quite making sense, a picture should make everything clear:

Once we've done this, we can (just as for the one-dimensional case) use array-like syntax to access our simulated multidimensional array. If we write
array[i][j] we're asking for the i'th pointer pointed to by array, and then for the j'th int pointed to by that inner

pointer. (This is a pretty nice result: although some completely different machinery, involving two levels of pointer dereferencing, is going on behind the scenes, the simulated, dynamically-allocated twodimensional ``array'' can still be accessed just as if it were an array of arrays, i.e. with the same pair of bracketed subscripts.) If a program uses simulated, dynamically allocated multidimensional arrays, it becomes possible to write ``heterogeneous'' functions which don't have to know (at compile time) how big the ``arrays'' are. In other words, one function can operate on ``arrays'' of various sizes and shapes. The function will look something like
func2(int **array, int nrows, int ncolumns) { } This function does accept a pointer-to-pointer-to-int, on the assumption that we'll only be calling it

with simulated, dynamically allocated multidimensional arrays. (We must not call this function on arrays like the ``true'' multidimensional array a2 of the previous sections). The function also accepts the dimensions of the arrays as parameters, so that it will know how many ``rows'' and ``columns'' there are,

so that it can iterate over them correctly. Here is a function which zeros out a pointer-to-pointer, twodimensional ``array'':
void zeroit(int **array, int nrows, int ncolumns) { int i, j; for(i = 0; i < nrows; i++) { for(j = 0; j < ncolumns; j++) array[i][j] = 0; } }

Finally, when it comes time to free one of these dynamically allocated multidimensional ``arrays,'' we must remember to free each of the chunks of memory that we've allocated. (Just freeing the top-level pointer, array, wouldn't cut it; if we did, all the second-level pointers would be lost but not freed, and would waste memory.) Here's what the code might look like:
for(i = 0; i < nrows; i++) free(array[i]); free(array);

24.1 Declaring, Assigning, and Using Function Pointers


Just as for data pointers, we can think of three steps involved in using function pointers. First, we must declare a variable which can hold a pointer to a function, and this ends up being a somewhat complex declaration. A simple function pointer declaration looks like this:
int (*pfi)(); This declares pfi as a pointer to a function which will return an int. As in other declarations, the *

indicates that a pointer is involved, and the parentheses () indicate that a function is involved. But what about the extra parentheses around (*pfi)? They're needed because there are precedence relationships in declarations just as there are in expressions, and when the default precedence doesn't give you what you want, you have to override it with explicit parentheses. In declarations, the () indicating functions and the [] indicating arrays ``bind'' more tightly than the *'s indicating pointers. Without the extra parentheses, the declaration above would look like
int *pfi(); /* WRONG, for pointer-to-function */

and this would declare a function returning a pointer to int. With the explicit parentheses, however, int (*pfi)() tells us that pfi is a pointer first, and that what it's a pointer to is a function, and what that function returns is an int. It's common to use typedefs (see section 18.1.6) with complicated types such as function pointers. For example, after defining
typedef int (*funcptr)();

the identifier funcptr is now a synonym for the type ``pointer to function returning int''. This typedef would make declaring pointers such as pfi considerably easier:
funcptr pfi;

(In section 18.1.6, we mentioned that typedefs were a little bit like preprocessor define directives, but better. Here we see another reason: there's no way we could define a preprocessor macro which would expand to a correct function pointer declaration, but the funcptr type we just defined using typedef will work just fine.) Once declared, a function pointer can of course be set to point to some function. If we declare some functions:
extern int f1(); extern int f2(); extern int f3(); then we can set our pointer pfi to point to one of them: pfi = &f1;

or to one or another of them depending on some condition:


if(condition) pfi = &f2; else pfi = &f3;

(Of course, we're not restricted to these two forms; we can assign function pointers under any circumstances we wish. The second example could be rendered more compactly using the conditional operator: pfi = condition ? &f2 : &f3 .) In these examples, we've used the & operator as we always have, to generate a pointer. However, when generating pointers to functions, the & is optional, because when you mention the name of a function but are not calling it, there's nothing else you could possibly be trying to do except generate a pointer to it. So, most programmers write
pfi = f1;

or
if(condition) pfi = f2; else pfi = f3;

(or, equivalently, using the conditional operator, pfi = condition ? f2 : f3 ). (The fact that a function pointer is generated automatically when a function appears in an expression but is not being called is very similar to, and in fact related to, the fact that a pointer to the first element of an array is generated automatically when an array appears in an expression.) Finally, once we have a function pointer variable which does point to a function, we can call the function that it points to. Broken down to a near-microscopic level, this, too, is a three-step procedure. First, we write the name of the function pointer variable:
pfi

This is a pointer to a function. Then, we put the * operator in front, to ``take the contents of the pointer'':
*pfi

Now we have a function. Finally, we append an argument list in parentheses, along with an extra set of parentheses to get the precedence right, and we have a function call:
(*pfi)(arg1, arg2)

The extra parentheses are needed here for almost exactly the same reason as they were in the declaration of pfi. Without them, we'd have
*pfi(arg1, arg2) /* WRONG, for pointer-to-function */

and this would say, ``call the function pfi (which had better return a pointer), passing it the arguments arg1 and arg2, and take the contents of the pointer it returns.'' However, what we want to do is take the contents of pfi (which is a pointer to a function) and call the pointed-to function, passing it the arguments arg1 and arg2. Again, the explicit parentheses override the default precedence, arranging that we apply the * operator to pfi and then do the function call. Just to confuse things, though, parts of the syntax are optional here as well. There's nothing you can do with a function pointer except assign it to another function pointer, compare it to another function pointer, or call the function that it points to. If you write
pfi(arg1, arg2)

it's obvious, based on the parenthesized argument list, that you're trying to call a function, so the compiler goes ahead and calls the pointed to function, just as if you'd written
(*pfi)(arg1, arg2)

When calling the function pointed to by a function pointer, the * operator (and hence the extra set of parentheses) is optional. I prefer to use the explicit *, because that's the way I learned it and it makes a bit more sense to me that way, but you'll also see code which leaves it out.

24.2 What are Function Pointers Good For?


In this section we'll list several situations where function pointers can be useful. A common problem is sorting, that is, rearranging a set of items into a desired sequence. It's especially common for the set of items to be contained in an array. Suppose you were writing a function to sort an array of strings. Roughly speaking, the algorithm for sorting the elements of an array looks like this:
for all pairs of elements in the array if the pair is out of order swap them

In C, the outline of the function might therefore look like this:


stringsort(char *strings[], int nstrings) { for( all pairs of elements i, j in strings )

{ if(strcmp(strings[i], strings[j]) > 0) { char *tmp = strings[i]; strings[i] = strings[j]; strings[j] = tmp; } } }

Remember, the library function strcmp compares two strings and returns either a negative number, zero, or a positive number depending on whether the first string was ``less than,'' the same as, or ``greater than'' the second string. If you haven't seen it before, the series of three assignments involving the temporary variable tmp is a standard idiom for swapping two items. (The more obvious pair of assignments
strings[i] = strings[j]; strings[j] = strings[i];

would just about as obviously not work, because the first line would obliterate strings[i] before the second line had a chance to assign it to string[j].) Naturally, a real sort function implementing a real sorting algorithm will be a bit more elaborate; in particular, the details of how we choose which pairs of elements to compare (and in what order) can make a huge difference in how efficiently the function completes its job. We'll show a complete example in a minute, but for now, our more important focus is on the comparison step. The final ordering of the strings will depend on the strcmp function's definition of what it means for one string to be ``less than'' or ``greater than'' another. How strcmp compares strings (as we saw in chapter 8) is to look at them a character at a time, based on their values in the machine's character set (which is how C always treats characters). In ASCII, the character set that most computers use, the codes representing the letters are in order, so strcmp gives you something pretty close to alphabetical order, with the significant difference that all the upper-case letters come before the lower-case letters. So a string-sorting function built around strcmp would sort the words ``Zeppelin,'' ``able,'' ``baker,'' and ``Charlie'' into the order
Charlie Zeppelin able baker strcmp is quite useless for sorting strings which consist entirely of digits, because it goes from left to

right, stopping when it finds the first difference, without regard to whether it was comparing the ten's digit of one number to the hundred's of another. For example, a strcmp-based sort would sort the strings "1", "2", "3", "4", "12", "24", and "234" into the order
1 12 2 234 24 3 4

Depending on circumstances, we might want our string sorting function to sort into the order that strcmp uses, or into ``dictionary'' order (that is, with all the a's together, both upper-case and lowercase), or into numeric order. We could pass our stringsort function a flag or code telling it which comparison strategy to use, although that would mean that whenever we invented a new comparison strategy, we would have to define a new code or flag value and rewrite stringsort. But, if we observe that the final ordering depends entirely on the behavior of the comparison function, a neater implementation is if we write our stringsort function to accept a pointer to the function which we want it to use to compare each pair of strings. It will go through its usual routine of comparing and exchanging, but whenever it makes the comparisons, the actual function it calls will be the function pointed to by the function pointer we hand it. Making it sort in a different order, according to a different comparison strategy, will then not require rewriting stringsort at all, but instead will just involve passing it a pointer to a different comparison function (after perhaps writing that function). Here is a complete implementation of stringsort, which also accepts a pointer to the string comparison function:
void stringsort(char *strings[], int nstrings, int (*cmpfunc)()) { int i, j; int didswap; do { didswap = 0; for(i = 0; i < nstrings - 1; i++) { j = i + 1; if((*cmpfunc)(strings[i], strings[j]) > 0) { char *tmp = strings[i]; strings[i] = strings[j]; strings[j] = tmp; didswap = 1; } } } while(didswap);

(This code uses a fairly simpleminded sorting algorithm. It repeatedly compares adjacent pairs of elements, keeping track of whether it had to exchange any. If it makes it all the way through the array without exchanging any, it's done; otherwise, it has at least made progress, and it goes back for another pass. This is not the world's best algorithm; in fact it's not far from the infamous ``bubble sort.'' Although our focus here is on function pointers, not sorting, in a minute we'll take time out and look at a simple improvement to this algorithm which does make it a decent one.) Now, if we have an array of strings
char *array1[] = {"Zeppelin", "able", "baker", "Charlie"};

we can sort it into strcmp order by calling


stringsort(array1, 4, strcmp);

Notice that in this call, we are not calling strcmp immediately; we are generating a pointer to strcmp, and passing that pointer as the third argument in our call to stringsort. If we wanted to sort these words in ``dictionary'' order, we could write a version of strcmp which ignores case when comparing letters:
#include <ctype.h> int dictstrcmp(char *str1, char *str2) { while(1) { char c1 = *str1++; char c2 = *str2++; if(isupper(c1)) c1 = tolower(c1); if(isupper(c2)) c2 = tolower(c2); if(c1 != c2) return c1 - c2; if(c1 == '\0') return 0; }

(The functions isupper and tolower are both from the standard library and are declared in <ctype.h>. isupper returns ``true'' if its argument is an upper-case letter, and tolower converts its argument to a lower-case letter.) With dictstrcmp in hand, we can sort our array a different way:
stringsort(array1, 4, dictstrcmp); (Some C libraries contain case-independent versions of strcmp called stricmp or strcasecmp. Both

of these would do the same thing as our dictstrcmp, although neither of them is standard.) We can also write another string-comparison function which treats the strings as numbers, and compares them numerically:
int numstrcmp(char *str1, char *str2) { int n1 = atoi(str1); int n2 = atoi(str2); if(n1 < n2) return -1; else if(n1 == n2) return 0; else return 1; }

Then, we can sort an array of numeric strings correctly:


char *array2[] = {"1", "234", "12", "3", "4", "24", "2"}; stringsort(array2, 7, numstrcmp);

(As an aside, you will occasionaly see code which is supposed to compare two integers and return a negative, zero, or positive result--i.e. just like the numstrcmp function above--but which does so by the seemingly simpler technique of just saying
return n1 - n2;

It turns out that this trick has a serious drawback. Suppose that n1 is 32000, and n2 is -32000. Then n1 - n2 is 64000, which is not guaranteed to fit in an int, and will overflow on some machines, producing an incorrect result. So the explicit comparison code such as numcmp used is considerably safer.) Finally, since we've started looking at sorting functions, let's look at a simple improvement to the string sorting function we've just been using. It has been plodding along comparing adjacent elements, so when an element is far out of place, it takes many passes to percolate it to the correct position (one cell at a time). The improvement, based on the work of Donald L. Shell, is to compare pairs of more widelyseparated elements at first, then proceed to compare more and more closely-situated elements, until on the last pass (or passes) we compare adjacent elements, as before. Since the earlier passes will have done more of the work (and more quickly), the later passes will just have to do the ``final cleanup.'' Here is the improvement:
void stringsort(char *strings[], int nstrings, int (*cmpfunc)()) { int i, j, gap; int didswap; for(gap = nstrings / 2; gap > 0; gap /= 2) { do { didswap = 0; for(i = 0; i < nstrings - gap; i++) { j = i + gap; if((*cmpfunc)(strings[i], strings[j]) > 0) { char *tmp = strings[i]; strings[i] = strings[j]; strings[j] = tmp; didswap = 1; } } } while(didswap); } }

The inner loops are the same as before, except that where we had before always been computing j = i + 1, now we compute j = i + gap, where gap is a new variable (controlled by a third, outer loop) which starts out large (nstrings / 2), and then diminishes until it's 1. Although this code contains three nested loops instead of two, it will end up making far fewer trips through the inner loop, and so will execute faster. The Standard C library contains a general-purpose sort function, qsort (declared in <stdlib.h>), which can sort any type of data (not just strings). It's a bit trickier to use (due to this generality), and you

almost always have to write an auxiliary comparison function. For example, due to the generic way in which qsort calls your comparison function, you can't use strcmp directly even when you're sorting strings and would be satisfied with strcmp's ordering. Here is an auxiliary comparison function and the corresponding call to qsort which would behave like our earlier call to stringsort(array1, 4, strcmp) :
/* compare strings via pointers */ int pstrcmp(const void *p1, const void *p2) { return strcmp(*(char * const *)p1, *(char * const *)p2); } ... qsort(array1, 4, sizeof(char *), pstrcmp); When you call qsort, it calls your comparison function with pointers to the elements of your array.

Since array1 is an array of pointers, the comparison function ends up receiving pointers to pointers. But qsort doesn't know that the array is an array of pointers; it's written so that it can sort arrays of anything. (That's why qsort's third argument is the size of the array element.) Since qsort doesn't know what the type of the elements being sorted is, it uses void pointers to those elements when it calls the comparison function. (The use of void pointers here recalls their use with malloc, where the situation is similar: malloc returns pointers to void because it doesn't know what type we'll use the pointers to point to.) In the ``wrapper'' function pstrcmp, we use the explicit cast (char * const *) to convert the void pointers which the function receives into pointers to (pointers to char) so that when we use one * operator to find out what they point to, we get one of the character pointers (char *) which we know our array1 array actually contains. We pass the resulting two char *'s to strcmp to do most of the work, but we have to do a bit of work first to recover the correct pointers. (The extra const in the cast (char * const *) keeps the compiler from complaining, since the pointers being passed in to the comparison function are actually const void *, meaning that although it may not be clear what they point to, it's guaranteed that we won't be using them to modify whatever it is they point to. We need to keep a level of const-ness in the converted pointers so that the compiler doesn't worry that we're going to accidentally use the converted pointers to modify what we shouldn't.) That was a rather elaborate first example of what function pointers can be used for! Let's move on to some others. Suppose you wanted a program to plot equations. You would give it a range of x and y values (perhaps -10 to 10 along each axis), and ask it to plot y = f(x) over that range (e.g. for -10 <= x <= 10). How would you tell it what function to plot? By a function pointer, of course! The plot function could then step its x variable over the range you specified, calling the function pointed to by your pointer each time it needed to compute y. You might call
plot(-10, 10, -10, 10, sin)

to plot a sine wave, and


plot(-10, 10, -10, 10, sqrt)

to plot the square root function. (If you were to try to write this plot function, your first question would be how to draw lines or otherwise do graphics in C at all, and unfortunately this is one of the questions which the C language doesn't answer. There are potentially different ways of doing graphics, with different system functions to call, for every different kind of computer, operating system, and graphics device.) One of the simplest (and allegedly least user-friendly) styles of user interfaces is the command line interface, or CLI. The user types a command, hits the RETURN key, and the system interprets the command. Often, the first ``word'' on the line is the command name, and any remaining words are ``arguments'' or ``option flags.'' The various shells under Unix, COMMAND.COM under MS-DOS, and the adventure game we've been writing are all examples of CLI's. If you sit down to write some code implementing a CLI, it's simple enough to read a line of text typed by the user, and simple enough to break it up into words. But how do you map the first word on the line to the code which implements that command? A straightforward, rather simpleminded way is to use a giant if/else chain:
if(strcmp(command, "agitate") == 0) { code for ``agitate'' command } else if(strcmp(command, "blend") == 0) { code for ``blend'' command } else if(strcmp(command, "churn") == 0) { code for ``churn'' command } ... else fprintf(stderr, "command not found\n");

This works, but can become unwieldy. Another technique is to write several separate functions, each implementing one command, and then to build a table (typically an array of structures) associating the command name as typed by the user with the function implementing that command. In the table, the command name is a string and the function is represented by a function pointer. With this table in hand, processing the user's command becomes a relatively simple matter of searching the table for the matching command string and then calling the corresponding pointed-to function. (We'll see an example of this technique in this week's assignment.) Our last example concerns larger, more elaborate systems which manipulate many types of data. (Here, by ``types of data,'' I am referring to data structures used by the program, presumably implemented as structs, but in any case more elaborate than C's basic data types.) It is often the case that similar operations must be performed on dissimilar data types. The conventional way of implementing such operations is to have the code for each operation look at the data type it's operating on and adjust its behavior accordingly; in the worst case, each piece of code (for each operation) will have a long switch statement or if/else chain switching among n separate, different ways of performing the operation on n different data types. If a new data type is ever added, new cases must be added to all segments of the code implementing all of the operations. Another way of organizing such code is to place one or more function pointers right there in the data structures describing each data type. These pointers point to functions which are streamlined and optimized for performing the operations on just one data type. (Each piece of data would obviously have

its pointer(s) set to point to the function(s) for its own data type.) This idea is one of the cornerstones of Object-Oriented Programming. We could use a version of it in our adventure game, too: rather than writing new, global pieces of code implementing each new command verb we want to let the user type, and then making each of those pieces of code check all sorts of attributes to ensure that the command can't be used on inappropriate objects (``break water with cup'', ``light candle with bucket,'' etc.) we could attach special-purpose pieces of code to the individual objects themselves (via function pointers, of course) and arrange that the code would only fire up if the player were trying to use the relevant object.

24.3 Function Pointers and Prototypes


It's generally a good idea to have a function prototype in scope whenever you call a function. Function prototypes allow the compiler to generate correct code for function calls, and to verify that you've called a function with the correct number and type of arguments. Standard library functions are prototyped by including the relevant standard header files, and it's generally recommended that you write prototype declarations for your own functions, too, usually placing those declarations in your own header files. But what prototype can be used for an indirect function call using a function pointer, such as
(*pfi)(arg1, arg2)

In general, it won't be known until run time what function pfi actually points to, so there's no way for the compiler to check the call against the prototype of the actually-called function. (We may know that pfi points to f1, f2, or f3, and we may have supplied prototypes for those functions, but that's immaterial.) We've seen that when you declare a function pointer, you must declare what the return value of the pointed-to function will be. It's also possible to specify what the prototype of the pointed-to function will be. Here's our earlier declaration of pfi, beefed up with a prototype for the arguments:
int (*pfi)(int, int);

Now we know that pfi is a pointer to a function, that the function (whatever it is) accepts two int arguments, and that the function returns an int. Having specified this, the compiler will now be able to do some more checking for us. If we call
(*pfi)(1, 2, 3)

the compiler will complain, because it knows that the function pointed to by pfi is supposed to receive two arguments, but we've passed three. The compiler is also in a position to verify that we actually set pfi to point to functions that accept two int arguments. Our examples so far were in terms of functions which we declared as
extern int f1(); extern int f2(); extern int f3();

that is, as functions taking unspecified arguments and returning int. (Remember, empty parentheses in an external function declaration indicate that the function accepts unspecified arguments, while empty

parentheses in a function definition indicate that the function accepts no arguments.) So the compiler won't be able to check the assignments unless we also provide prototypes:
extern int f1(int, int); extern int f2(int, int); extern int f3(int, int);

Now, when we assign


pfi = f1;

the compiler can verify that the function pointer being assigned is to a function which accepts two int arguments, as pfi expects. If, on the other hand, we declared and assigned
extern int x(int); pfi = x;

the compiler would complain, because pfi is supposed to point to a function which accepts two int arguments, and the eventual call to (*pfi)() is going to be verified to be a call passing two arguments, so assigning pfi to point to x (which accepts a single argument) is incorrect.

Вам также может понравиться