Function pointers in C
Pascal Cuoq - 24th Aug 2013This 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.