Return to the lecture notes index

March 18, 2008 (Lecture 15)

Today's Example

Printing Items From the Linked List

As we left off last class, we had implemented an addHead(...) method. But, we ran ito a problem. We had no way of testing it. So, we went about the business of putting together the printLL(..) method.

But, we ran into a big problem -- we really couldn't get the job done. As a linked list, we've got no idea what's inside -- it is just a blob at an address to us. We've got a "void pointer", a.k.a, "Generic pointer" to us. And, this is significant. Without knowing the type, we've got no idea how to print it.

We don't know if it is an int, float, char, or the like -- or if it is a complicated struct. And, especially if it happens to be a struct, we could have a mess -- it could have pointers to structures to pointers.

What should be printed? How? How do we know? We don't know -- and we can't figure it out. We're stuck here. Quite simply, trying to figure it out is a lost cause. The caller is going to have to help us out here.

Wishing for Java's toString()

If we were coding in Java, this wouldn't be a problem. We'd make use of the toString() method. Remember it? It was one of those canonical methods associated with every object. And, when invoked, it would serialize the object, representing it as a String that could, for example, be printed.

We'd really like to have such a thing here, wouldn't we. Let's, for the moment, pretend that we can get such a toString() method into this function. Life remains complicated. Unlike Java, C isn't a garbage collected language and doesn't have a first-class string type.

We can write some function that takes a pointer to the data in and gives the user back a pointer to a "C string" representation. But, how is that string allocated and who frees it? The caller could pass in a buffer, but what if it isn't big enough?

The program can malloc() the space -- but then who frees it? The print function? There isn't a natural destroy method here. It would have to be the print function. And, that isn't good. It violates our convention that space should always be freed by the party that created it.

As it turns out, we really don't need a string. We really just need to get the datum printed. So, what we'll do instead is let the caller give us a way of printing the datum. But, how do we do that?

Function pointers

Well, functions live in memory like anything else. And, C allows us to call functions via pointers. So, the caller can pass in a pointer to the print function. And, we can then call this function via the pointer, and print out the item. And, the cool thing about this is that the linked list never needs to know the organization of the data or how to print it.

So, let's consider a print function such as the one below. It takes a FILE * so that it can print to any file -- including stdio. Notice also that it accepts a "void *" and casts it to an "int *", not an "int *". This makes sure that we don't get a warning because the type doesn't match. Remember, we can cast inside this method, because we know we're going form a "void *" to an "int *". But, we can't cast inside printLL(...), because it doesn't know the data's type -- so it can't do the cast. In class, we added this function to the main program's .c file, not the linked list library. And, this makes sense -- it is tied to the user's data -- not the linked list, itself:

  int printInt (void *item, FILE *fp) {
    int number = *((int *)item);

    fprintf (fp, "%d\n", number);

    return 0;
  }
  

Declaring a Function pointer

So, given the function as declared above, how do we declare the function pointer? We do it the same way that we declare a pointer to any other type. We take the declaration of the base type and we add an *-asterisk before the identifier name.

In the context of a function pointer, the declaration of the base type includes the return type and the argument list. So, in the case of our print function, we can declare a pointer capable of pointing to it as follows:

  int (*printfn) (void *, FILE *)
  

This follows the general form of the declaration of a function pointer:

  return_type (*identifier_name) (argument_list)
  

The only think that is a bit strange is the extra set of parenthesis -- those that surround the "starred identifier". These parenthesis are necessary so that the compiler knows that we are declaring a function pointer -- and not providing a forward reference to a function that returns a pointer.

Consider the following example. Notice that it returns a pointer to an int -- the *-asterisk associates with the return type.

  int * something(int x, int y);
  

Now, consider this example. It declares a variable, something, that is a pointer to a function that returns an int. Notice how the ()-parenthesis shift the association of the *-asterisk away from the return value such that we create a pointer type:

  int * something(int x, int y);
  

Invoking Functions Via Pointers

Given a function pointer, the function can be called just as usual:

  int one = 1;
  int (*printfn)(void *, FILE *);

  printfn = printInt; /* Function's don't need the &, they are implicitly addresses */

  printfn (&one, stdio); /* You could use (*printfn)(&one, stdio), but this isn't necessary -- or, technically, even correct */
  

The Linked List's Revised, Generic Linked List Print Function

Now that we are armed with a print function and an understanding of how to declare and use function pointers, we can rewrite our linked list's print function. Notice that it accepts a pointer to a caller-supplied print function and uses it to print the data -- without ever knowing how the data is organized:

  int printLL (linkedlist *list, int (*printfn)(void *, FILE *)) {
    node *index;

    for (index=list->head; index; index=index->next) {
      printfn(index->data, stdout); /* Notice the use of the function pointer */
      printf ("\n");
    }

    return SUCCESS_LL;
  }
  

Some Good things To Try

Try writing the following for practice: