Otringal
Otringal

Reputation: 131

Function pointers - why, when I can do without?

Disclaimer: I've read countless other articles on this topic and I still don't get them. Example: why do this:

void func(int a, void (*callback)(int))
{
    /* do something with a and callback */
    callback(3);
}

void pointme(int b)
{
    /* do something with b */
}

int main()
{
    void (*pf)(int);
    pf = &pointme;
    func(10, pf);
}

when I can simply do this:

void func(int a)
{
    pointme(3);
    /* do something with a*/
}

void pointme(int b)
{
    /* do something with b */
}

int main()
{
    func(10);
}

??? I really don't get it. Any help would be super-appreciated. Thanks!!!

Upvotes: 4

Views: 1602

Answers (3)

abelenky
abelenky

Reputation: 64672

For a short simple example, check out function qsort.

void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));

The 4th parameter is a function pointer.

It is a function that can sort any array of any type of data, provided that the programmer (you) provide a much simpler function that simply compares two items and indicates which one is bigger.

Function qsort clearly does not know about your type of data. And you don't need to know the complexities of sorting data quickly and efficiently. But as long as you provide a function to do the comparison, and the standard library provides a sort routine, the two work together to accomplish a powerful task.

Upvotes: 2

John Bode
John Bode

Reputation: 123448

In C, function pointers allow you to do the following:

  • Create plug-in architectures;
  • Create "generic" functions and data structures;

and a few others that I'm not going to go into.

Plug-ins

If you've used any sort of image editor, audio editor, browser, etc., you've probably used some kind of plug-in; that is, some little piece of code that wasn't part of the original application, but is provided by a third-party library, allowing you to add new functionality to the app without having to upgrade or rebuild. This is done by packaging code into shared or dynamically linked libraries (.dll files on Windows, .so files on Linux). The program can load the contents of that file at runtime, then execute functions contained within that library.

A real-world example would take more space and time than we have, but here's a toy program and library that illustrate the concept:

/**
 * lib1.c
 *
 * Provides functions that are called by another program
 */
#include <stdio.h>

static char *names[] = {"func1", "func2", NULL};

char **getNames( void ) { return names; }
void func1( void )      { printf( "called func1\n" ); }
void func2( void )      { printf( "called func2\n" ); }

The getNames function basically gives me an inventory of what functions are available in the library for me to call (there are about a thousand better ways to do this, but you should get the point).

To build this into a shared library, I do the following:

gcc -o lib1.so -std=c99 -pedantic -Wall -Werror -fPIC -shared lib1.c

This creates the shared library file lib1.so.

Now I add a simple driver:

#include <stdio.h>
#include <dlfcn.h>

int main( void )
{
  /**
   * Open the shared library file
   */
  void *lib1handle = dlopen( "lib1.so", RTLD_LAZY | RTLD_LOCAL );

  /**
   * Load the "getNames" function into the current process space
   */
  char **(*libGetNames)( void ) =  dlsym( lib1handle, "getNames" );
  if ( libGetNames )
  {
    /**
     * call the "getNames" function in the shared library
     */
    char **names = libGetNames();
    while ( names && *names)
    {
      printf( "calling %s\n", *names );
      /**
       * Load each named function into the current process space
       * and execute it
       */
      void (*func)(void) =  dlsym( lib1handle, *names++ );
      if ( func )
        func();
    }
  }
  dlclose( lib1handle );
  return 0;
}

I built this code as follows:

gcc -o main -std=c99 -Wall -Werror main.c -ldl

Note that the lib1.so file isn't part of the build command; the main program doesn't know about that library code until it is run.

You'll also have to put the current directory in the LD_LIBRARY_PATH variable, otherwise dlopen won't find the library:

[fbgo448@n9dvap997]~/prototypes/dynlib: export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

All this code does is get the list of function names via the getNames function in the library, then it loads and executes each function in the library in turn. The libGetNames function pointer will point to the getNames function in the library, and the func function pointer will point to each of func1 and func2 in turn. When run, it produces the following output:

[fbgo448@n9dvap997]~/prototypes/dynlib: ./main
calling func1
called func1
calling func2
called func2

Exciting, right? But this is pretty much how apps like Photoshop and Audacity and others allow you to extend their functionality without having to upgrade or rebuild or whatever; you just download the right library, put it in the right place, and the app will load the contents of that library and make that code available to you.

Sure, you could statically link the library with main and call the functions directly, but the beauty of the shared library concept is that it allows you to add new functions to main without having to touch main itself.

"Generic" Functions and Data Structures

The canonical example of a "generic" function in C is the qsort function. Using qsort, you can sort arrays of any type; all you have to do is provide a function that does the actual comparison of elements in the array. Again, a stupid example:

#include <stdio.h>
#include <stdlib.h>

int cmpInt( const void *lhs, const void *rhs )
{
  const int *l = lhs, *r = rhs;
  return *l - *r;
}

int cmpFloat( const void *lhs, const void *rhs )
{
  const float *l = lhs, *r = rhs;
  return *l - *r;
}

char *fmtInt( char *buffer, size_t bufsize, const void *value )
{
  const int *v = value;
  sprintf( buffer, "%*d", (int) bufsize, *v );
  return buffer;
}

char *fmtFloat( char *buffer, size_t bufsize, const void *value )
{
  const float  *v = value;
  sprintf( buffer, "%*.*f", (int) bufsize, 2, *v );
  return buffer;
}

void display( const void *data, size_t count, size_t size, char *(*fmt)(char *, size_t, const void *))
{
  const char *d = data;
  char buffer[10];
  printf( "{%s", fmt( buffer, sizeof buffer, &d[0] ));
  for ( size_t i = size; i < count * size; i += size )
    printf( ", %s", fmt( buffer, sizeof buffer, &d[i] ));
  printf( "}\n" );
}

int main( void )
{
  int   iarr[] = {9, 100, 53, 99, 4, 29, 44};
  float farr[] = {9, 100, 54, 99, 4, 29, 44};

  printf( "iarr before sort: " );
  display( iarr, sizeof iarr / sizeof *iarr, sizeof *iarr, fmtInt );
  qsort( iarr, sizeof iarr / sizeof *iarr, sizeof *iarr, cmpInt );
  printf (" iarr after sort: " );
  display( iarr, sizeof iarr / sizeof *iarr, sizeof *iarr, fmtInt );

  printf( "farr before sort: " );
  display( farr, sizeof farr / sizeof *farr, sizeof *farr, fmtFloat );
  qsort( farr, sizeof farr / sizeof *farr, sizeof *farr, cmpFloat );
  printf (" farr after sort: " );
  display( farr, sizeof farr / sizeof *farr, sizeof *farr, fmtFloat );

  return 0;
}

Again, not very exiting - this code defines two arrays, one int and one float, displays them, sorts them, then displays them again:

[fbgo448@n9dvap997]~/prototypes/dynlib: ./sorter
iarr before sort: {         9,        100,         53,         99,          4,         29,         44}
 iarr after sort: {         4,          9,         29,         44,         53,         99,        100}
farr before sort: {      9.00,     100.00,      54.00,      99.00,       4.00,      29.00,      44.00}
 farr after sort: {      4.00,       9.00,      29.00,      44.00,      54.00,      99.00,     100.00}

However, I'm decoupling type information from the basic sorting and display logic. qsort doesn't need to know the types of its elements, it only needs to know whether one element compares "less than" or "equal to" another. It calls the cmpInt and cmpFloat functions to perform the actual comparison; none of the other logic requires type information. I don't have to replicate the guts of the sorting algorithm for each different type (sort_int, sort_float, sort_foo); I only have to provide the right comparison function to qsort.

Similarly, all the display function is doing is printing out a comma-separated list of strings, surrounded by { and }. It lets the fmtInt and fmtFloat worry about the details of how ints and floats are formatted. I don't have to replicate any of the display logic for the different types.

By now you've probably noticed that I keep putting "generic" in scare quotes. The problem is that you have to pass the address of everything as a void *, meaning you're throwing type safety out the window. The compiler can't protect me against passing the wrong comparison or formatting function for a given array; I'll just get garbled output (or a runtime error). Languages like C++ and Java and C# provide templating capablities that allow you to write generic code yet still maintain type safety (i.e., the compiler will still be able to yell at you if you're using the wrong type).

There are other uses for function pointers, but I've already spent way too much time and energy on this answer than I should have.

Upvotes: 2

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726479

when I can simply do this [...]

That's correct, you should call the function directly if you can. However, there are situations when you cannot make a direct call, because the function that you are trying to call does not exist in your code. Of course, the function will be there in the finished program, but in many situations you would develop a library that interacts with other people's code, and needs to compile by itself.

In addition, there are situations when you can call the function directly, but you do not want to do it to avoid code repetition.

This is when function pointers come in handy: the caller can tell your function which of his functions to call.

Consider designing a threading library that lets users run their functions in parallel. This library cannot make direct references to user code, for two reasons:

  • Your code has no idea which functions the users are going to run concurrently, and
  • You do not want to write a separate function for each kind of function that your users may decide to pass to your library.

Upvotes: 7

Related Questions