Return to the lecture notes index

October 16, 2007 (Lecture 12)

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;
  }
  

So, given this function, what is the type fo the pointer?

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

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

  return_type (*identifier_name) (argument_list)
  

Given a function pointer, the function can be called just as it would be normally. Consider the example below:

  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 */
  

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: