Why can't I cast a function pointer to (void *)?
I have a function that takes a string, an array of strings, and an array of pointers, and looks for the string in the array of strings, and returns the corresponding pointer from the array of pointers. Since I use this for several different things, the pointer array is declared as an array of (void *), and the caller should know what kind of pointers are actually there (and hence what kind of a pointer it gets back as the return value).
When I pass in an array of function pointers, however, I get a warning when I compile with -Wpedantic
:
clang:
test.c:40:8: warning: assigning to 'voidfunc' (aka 'void (*)(void)') from 'void *' converts
between void pointer and function pointer [-Wpedantic]
gcc:
test.c:40:8: warning: ISO C forbids assignment between function pointer and ‘void *’ [-Wpedantic]
fptr = find_ptr("quux", name_list, (void **)ptr_list,
Here's a test file, which despite the warning does correctly print "quux":
#include <stdio.h>
#include <string.h>
void foo(void)
{
puts("foo");
}
void bar(void)
{
puts("bar");
}
void quux(void)
{
puts("quux");
}
typedef void (* voidfunc)(void);
voidfunc ptr_list[] = {foo, bar, quux};
char *name_list[] = {"foo", "bar", "quux"};
void *find_ptr(char *name, char *names[], void *ptrs[], int length)
{
int i;
for (i = 0; i < length; i++) {
if (strcmp(name, names[i]) == 0) {
return ptrs[i];
}
}
return NULL;
}
int main() {
voidfunc fptr;
fptr = find_ptr("quux", name_list, (void **)ptr_list,
sizeof(ptr_list) / sizeof(ptr_list[0]));
fptr();
return 0;
}
Is there any way to fix the warning, other than not compiling with -Wpedantic
, or duplicating my find_ptr function, once for function pointers and once for non-function pointers? Is there a better way to achieve what I'm trying to do?
You can't fix the warning. In fact, in my opinion it should be a hard error since it's illegal to cast function pointers to other pointers because there are architectures out there today where this isn't just a violation of the C standard but an actual error that will make the code not work. Compilers allow it because many architectures get away with it even though those programs will crash badly on some other architectures. But it's not just a theoretical standard violation, it's something that causes real bugs.
For example on ia64 function pointers are (or at least used to be last time I looked) actually two values, both necessary to make function calls across shared libraries or a program and a shared library. Likewise, the common practice to cast and call function pointers to functions returning a value to a pointer to a function returning void because you know you'll ignore the return value anyway is also illegal on ia64 because that can lead to trap values leaking into registers causing crashes in some unrelated piece of code many instructions later.
Don't cast function pointers. Always have them match types. This is not just standards pedantry, it's an important best practice.
One solution is to add a level of indirection. This helps with lots of things. Instead of storing a pointer to a function, store a pointer to a struct
storing a pointer to a function.
typedef struct
{
void (*ptr)(void);
} Func;
Func vf = { voidfunc };
ptrlist[123] = &vf;
etc.
This is something that has long been broken in the C standard and has never been fixed -- there is no generic pointer type that can be used for pointers to functions and pointers to data.
Before the C89 standard, all C compilers allowed converting between pointers of different types, and char *
was generally used as a generic pointer that might point to any data type or any function. C89 added void *
, but put in a clause that only object pointers could be converted to void *
, without ever defining what an object is. The POSIX standard fixes this issue by mandating that void *
and function pointers are safely convertable back and forth. So much code exists that converts function pointers to void *
and expects it to work properly. As a result, pretty much all C compilers still allow it, and still generate the correct code, as any compiler that did not would be rejected as unusable.
Strictly speaking, if you want to have a generic pointer in C, you need to define a union that can hold either a void *
or a void (*)()
and use an explicit cast of the function pointer to the correct function pointer type before calling it.