By pascal on Saturday, August 24 2013, 09:31 - Permalink

This post contains a complete list of everything a C program can do with a function pointer, for a rather reasonable definition of “do”. Examples of things not to do with a function pointer are also provided. That list, in contrast, is in no way exhaustive.

What a C program can do with a function pointer

Convert it to a different function pointer type

A function pointer can be converted to a different function pointer type. The C99 standard's clause 6.3.2.3:8 starts:

“A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer.”

Call the pointed function with the original type

Clause 6.3.2.3:8 continues:

“If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.”

Alright, so the above title is slightly sensationalistic: the pointed function can be called with a compatible type. After typedef int t; , the types t and int are compatible, and so are t (*)(t) and int (*)(int) , the types of functions taking a t and returning a t and of functions taking an int and returning an int , respectively.

There is no third thing a C program can do with a function pointer

Seriously. The C99 standard has uintptr_t , a recommended integer type to convert data pointers to, but there is not even an equivalent integer type to store function pointers.

What a C program cannot do with a function pointer

Convert it to an ordinary pointer

Function pointers should not be converted to char * or void * , both of which are intended for pointers to data (“objects” in the vocabulary of the C standard). Historically, there has been plenty of reasons why pointers to functions and pointers to data might not have the same representation. With 64-bit architectures, the same reasons continue to apply nowadays.

Call the pointed function with an incompatible type

Even if you know that type float is 32-bit, the same as int on your platform, the following is undefined:

void f(int x); int main(){ void (*p)(float) = f; (*p)(3); }

The line void (*p)(float) = f; , which defines a variable p of type “pointer to function that takes a float”, and initializes it with the conversion of f , is legal as per 6.3.2.3:8. However, the following statement, (*p)(3); is actually equivalent to (*p)((float)3); , because the type of p is used to decide how to convert the argument prior to the call, and it is undefined because p points to a function that requires an int as argument.

Even if you know that the types int and long are both 32-bit and virtually indistinguishable on your platform (you may be using an ILP32 or an IL32P64 platform), the types int and long are not compatible. Josh Haberman has written a nice essay on this precise topic.

Consider the program:

void f(int x); int main(){ void (*p)(long) = f; (*p)(3); }

This time the statement is equivalent to (*p)((long)3); , and it is undefined, even if long and int are both 32-bit (substitute long and long long if you have a typical I32LP64 platform).

Lastly, the example that prompted this post was, in a bit of Open-Source code, the creation of a new execution thread. The example can be simplified into:

void apply(void (*f)(void*), void *arg) { f(arg); } void fun(int *x){ // work work *x = 1; } int data; int main(){ apply(fun, &data); }

The undefined behavior is not visible: it takes place inside function apply() , which is a standard library function (it was pthread_create() in the original example). But it is there: the function apply() expects a pointer to function that takes a void* and applies it as such. The types int * and void * are not compatible, and neither are the types of functions that take these arguments.

Note that gcc -Wall warns about the conversion when passing fun to apply() :

t.c:11: warning: passing argument 1 of ‘apply’ from incompatible pointer type

Fixing this warning with a cast to void (*)(void*) is a programmer mistake. The bug indicated by the warning is that there is a risk that fun() will be applied with the wrong type, and this warning is justified here, since fun() will be applied with the wrong type inside function apply() . If we “fix” the program this way:

$ tail -3 t.c int main(){ apply((void (*)(void*))fun, &data); } $ gcc -std=c99 -Wall t.c $

The explicit cast to (void (*)(void*) silences the compiler, but the bug is still in the same place, in function apply() .

Fortunately gcc -std=c99 -Wall is not the only static analyzer we can rely on. Frama-C's value analysis warns where the problem really is, in function apply() , and it warns for both the version with implicit conversion and the version with explicit cast:

$ frama-c -val t.c … [value] computing for function apply <- main. Called from t.c:14. t.c:3:[value] warning: Function pointer and pointed function 'fun' have incompatible types: void (void *) vs. void (int *x). assert(function type matches)

The correct way to use function apply() without changing it is to make a function with the correct type for it, and to pass that function to apply() :

void stub(void *x){ fun(x); } … apply(stub, &data);

Note that in the above, x is implicitly converted when passed to function fun() , the same way that &data is implicitly converted to void* when passed to apply() .

Conclusion

There is almost nothing you can do in C with a function pointer. The feature is still very useful and instills a bit of genericity in an otherwise decidedly low-level language.

Function pointers are not often used in the standard library, considering: qsort() is, with pthread_create() , another of the few functions that requires a function pointer. Like it, it is often misused: it has its own entry in the C FAQ.

Jens Gustedt provided advice in the writing of this post.