WORD PTR

Pointed Development

C Function Pointers

| Comments

Introduction

Behold! The function pointer:

1
2
3
int (*fn1)();
void (*fn2)(int a, int b) = 0;
void *(*fn3)(int a, int (*f)(char *c, int l)) = &ex;

At first glance the syntax appears messy. Complicated. Maybe a little dangerous. Indeed, the word “pointer” alone conjures up images of null references and segfaults and all-night debugging sessions looking for unusual bugs. Function pointers, however, are not nearly as daunting as they may appear. They’re an amazingly useful, flexible device for any programmer.

In this post, I’m going to deep-dive into C function pointers.

Grammar

Let’s first discuss the C grammar for function pointers. The grammar below (slightly modified for brevity) comes from the YACC ANSI C grammar as described by Jutta Degener on her page, The YACC ANSI C grammar. I defer you to her page for grammar specifics.

declarator
  : pointer direct_declarator | direct_declarator
  ;
direct_declarator
  : IDENTIFIER
  | '(' declarator ')'
        | direct_declarator '(' parameter_type_list ')'
        ...
  ;

The grammar for a function pointer in C is very simple. It simply takes some getting used to: Surround the ubiquitous C pointer character and an identifier in parenthesis, followed by a parameter type list also in parenthesis; the return type of the function is declared on the left-hand side of the function pointer name. For example:

1
void (*fn)(int a, int b);

This example declares fn to be a pointer to a function which accepts two int parameters, a and b. The pointed-to function returns void.

We can set a function pointer to the address of a function through assignment:

1
2
3
4
5
int ex(int a) { return a*a; }
int main(int argc, char* argv) {
  int (*fn)(int) = &ex; // *fn is now the address of ex.
  return fn(2);
}

We’ve declared a function ex and a function pointer fn and assigned fn the address of the function ex using the address-of operator, &

Function pointers may also be called through dereference, such as:

1
return (*fn)(2);

Personally, I prefer the short form: return fn(2);

Example

Examine the following code which demonstrates a callback:

1
2
void qsort(void *base, size_t nmemb, size_t size,
       int(*compar)(const void *, const void *));

This is the qsort function prototype as declared in stdlib.h (see man 3 qsort). The last parameter, named compar, is a function pointer which accepts two void pointers and returns an int:

1
int(*compar)(const void *, const void *)

Now review the following program to sort a collection of “widgets” utilizing qsort and a custom compare function:

qsort example
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdlib.h>
#include <stdio.h>

// declare a widget type and create a collection of widgets:
struct widget {
    char *bar;
};
struct widget widgets[] = {
    {"this\n"},{"is\n"},{"a\n"},{"test\n"}
};

// a custom compare function, comparing the bar members of widgets:
int compare_widgets(const void *w1, const void *w2) {
  return strcmp(((struct widget *)w1)->bar
           , ((struct widget *)w2)->bar);
}
int main(int argc, char* argv) {
    int i = 0;
    int count = sizeof(widgets) / sizeof(struct widget);

    // sort the array of widgets, passing the address of the custom compare function:
    qsort(widgets, count, sizeof(struct widget)
        , &compare_widgets);

    // Print out the results:
    for(; i < count; i++)
        fprintf(stdout, "%s", widgets[i].bar);
}

The important pieces are on lines 13 through 15 and 22 through 23. Lines 13 through 15 contain the function compare_widgets, which takes two void pointers and returns an int. Here, strcomp is utilized to compare the bar member of the widget struct. The qsort function on lines 22 through 23 takes as parameters the array widgets, the size of the array and the size of the members of the array, and finally a function pointer to a compare function, in this case compare_widgets. As qsort iterates over the items in the array, it calls the function pointed to by the function pointer to determine if the array elements are less than, equal to or greater than one another.

Details

Let’s look deeper into the implementation of C function pointers. Warning: The disassembly output below is more for guidance. Do not assume your compiler will produce the same code on your platform! Indeed, these examples are provided to demonstrate one possible implementation of function pointers at the machine level. Your mileage may vary.

All examples were built like so:

1
2
3
4
#!/bin/sh
# Note: gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5) on Ubuntu x86_64.
gcc -S -fverbose-asm -g -O0 test4.c -o test4.s
as -alhnd test4.s > test4.1st

Assuming the following code revisited from above:

1
2
3
4
5
int ex(int a) { return a*a; }
int main(int argc, char* argv) {
    int (*fn)(int) = &ex;
   return fn(2);
}

… where we once again define an example method (ex) and assign the function pointer fn to the address of ex, we simply return the value back to the caller of main.

Note the optimization option, -O0 (no optimizations): In all likelihood your compiler will handle function pointers in a much more optimized fashion.

Viewing the disassembly output from GCC of the ex method yields… unsurprising results (for brevity, I’ve removed the assembler directives from the following examples):

1
2
3
4
5
6
7
8
9
10
ex:
    pushq   %rbp    #
    movq    %rsp, %rbp      #,
    movl    %edi, -4(%rbp)  # a, a

    movl    -4(%rbp), %eax  # a, tmp60

    imull   -4(%rbp), %eax  # a, D.1592
    leave
    ret

Lines 2 through 4 save off the current frame pointer and set a new frame pointer while line 6 sets up the return value.

Lines 8 perform the multiplication from the parameter on the stack putting the result in EAX, the accumulator.

The interesting work is in the disassembly of main:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
main:
    pushq   %rbp    #
    movq    %rsp, %rbp      #,
    subq    $32, %rsp       #,
    movl    %edi, -20(%rbp) # argc, argc
    movq    %rsi, -32(%rbp) # argv, argv

    movq    $ex, -8(%rbp)   #, fn

    movq    -8(%rbp), %rax  # fn, tmp61
    movl    $2, %edi        #,
    call    *%rax   # tmp61

    leave
    ret

The stack frame and main parameters are established between lines 2 – 6. So we come to line 8 which copies the address of ex into local variable fn (on the stack at -8(%rbp), the first variable on the current frame).

Beginning on line 10, the address in fn is copied to a temporary location (register RAX), constant value 2 is copied into register EDI, and – finally – on line 12, we transfer control to the register address copied into register RAX. This is the function pointer. This is the magic.

1
2
3
4
    movq    $ex, -8(%rbp)   #, fn
    movq    -8(%rbp), %rax  # fn, tmp61
    movl    $2, %edi        #,
    call    *%rax   # tmp61

Here we see the setup and call of the function. This unoptimized disassembly is not much different from an unoptimized standard function call:

1
2
    movl    $2, %edi        #,
    call    ex      #

Here we copy the constant value 2 into register EDI and transfer control to the ex function. We avoid two MOV instructions under -O0 with GCC concerning the function pointer.

Just for giggles, lets see the output the same code with the optimization option of -O2:

1
2
3
main:
    movl    $4, %eax        #,
    ret

Oh.

Tips and Tricks

We’ve already utilized a function pointer as a parameter to a function to implement a callback. We can do so much more, however. How about an object interface? This is akin to writing object oriented code without virtual methods, though it could be easily accomplished by implementing our own vtable. I leave this as an exercise for later!

Consider the following interface:

1
2
3
4
5
6
typedef struct user_t {
    status_t (*create)  (const struct user_t *self);
    status_t (*read  )  (const struct user_t *self, struct user_t **user_out);
    status_t (*update)  (const struct user_t *self);
    status_t (*delete)  (const struct user_t *self);
 } user_t;

We’re interested in the method interface: create, read, update and delete which all take a pointer to “self.” As an aside, “self” is the “this” pointer in C++, however I didn’t want to muddy the waters by introducing a C++ idiom in a discussion concerning C. Apologies to purist!

We’ve defined four methods to read a user, create a user, update a user or delete a user. These function pointers must be defined and assigned when we instantiate a new instance (so to speak) of our user_t type.

Here’s a snippet of a fictional implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct sqlite3_vfs {
    // state removed...
    int (*xOpen)(sqlite3_vfs*, const char *zName, sqlite3_file*, int flags, int *pOutFlags);
    int (*xDelete)(sqlite3_vfs*, const char *zName, int syncDir);
    int (*xAccess)(sqlite3_vfs*, const char *zName, int flags, int *pResOut);
    int (*xFullPathname)(sqlite3_vfs*, const char *zName, int nOut, char *zOut);
    void *(*xDlOpen)(sqlite3_vfs*, const char *zFilename);
    void (*xDlError)(sqlite3_vfs*, int nByte, char *zErrMsg);
    void (*(*xDlSym)(sqlite3_vfs*,void*, const char *zSymbol))(void);
    void (*xDlClose)(sqlite3_vfs*, void*);
    int (*xRandomness)(sqlite3_vfs*, int nByte, char *zOut);
    int (*xSleep)(sqlite3_vfs*, int microseconds);
    int (*xCurrentTime)(sqlite3_vfs*, double*);
    int (*xGetLastError)(sqlite3_vfs*, int, char *);
    int (*xCurrentTimeInt64)(sqlite3_vfs*, sqlite3_int64*);
};

By using a common interface such as this, the SQLite core engine can defer implementation-specific details to the file system utilized by target OS. This provides for a great deal of flexibility and improves maintainability of the core: SQLite doesn’t care how a file is written to disk, or the underlying call made to read a file. It simply utilizes this generic interface.

Conclusion

Function pointers are a versatile construct in C and many other languages, such as delegates in .NET. I’ve discussed their syntax, provided an example and examined the low-level implementation details of function pointers. I sincerely hope you find this post useful. Please let me know if you find any incorrect information.

Thank you for reading!

Source for this post is available on GitHub.

Comments