Why can't I put a pointer on a function (void *)?

I have a function that takes a string, an array of strings and an array of pointers and searches for a string in an array of strings and returns the corresponding pointer from an array of pointers. Since I use this for several different things, an array of pointers is declared as an array (void *), and the caller needs to know which pointers actually exist (and therefore which pointer it returns as the return value).

However, when I pass in an array of function pointers, I get a warning when compiling with -Wpedantic :

clank:

 test.c:40:8: warning: assigning to 'voidfunc' (aka 'void (*)(void)') from 'void *' converts between void pointer and function pointer [-Wpedantic] 

NCA:

 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 is a test file that, despite the warning, correctly prints "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 a way to fix the warning, except to not compile with -Wpedantic , or duplicate 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?

+10
c pointers function-pointers
source share
6 answers

You cannot correct this warning. In fact, in my opinion, this should be a tough mistake, since it is illegal to throw pointers to other pointers, because today there are architectures where it is not just a violation of the C standard, but an actual error that will make the code not work. Compilers allow this because many architectures move away from it, even if these programs do a lot of harm on some other architectures. But this is not just a theoretical standard violation, it is what causes real errors.

For example, the ia64 function pointers (or at least the last time I looked at it) actually have two values ​​needed to make function calls in shared libraries or in a program and in a shared library. Similarly, the usual practice of casting functions and function pointers to functions that return a value, a pointer to a function that returns void, because you know that in any case you ignore the return value, is also illegal on ia64, as this can lead to data leakage into registers causing failures in some unrelated piece of code, many instructions later.

Do not use function pointers. Always have matching match types. This is not just standard pedantry, it is an important good practice.

+12
source share

One solution is to add a level of indirection. It helps with a lot of things. Instead of saving a pointer to a function, save a pointer to a struct by pointing to a pointer to a function.

 typedef struct { void (*ptr)(void); } Func; Func vf = { voidfunc }; ptrlist[123] = &vf; 

and etc.

+6
source share

This is something that has long been violated in the C standard and has never been fixed - there is no universal type of pointer that could be used for function pointers and data pointers.

Prior to the C89 standard, all C compilers allowed conversion between pointers of different types, and char * usually used as a universal pointer that can point to any data type or any function. C89 added void * , but included the suggestion that only object pointers can be converted to void * , without even specifying what the object is. The POSIX standard addresses this issue by requiring void * and function pointers to be safely convertible back and forth. There is so much code that converts function pointers to void * and expects it to work correctly. As a result, almost all C compilers still allow this and still generate the correct code, since any compiler that would not be rejected would be rejected as unusable.

Strictly speaking, if you want to have a universal pointer in C, you need to define a union that can contain void * or void (*)() and use the explicit cast of the function pointer to the correct type of function pointer before calling.

+6
source share

The reason for the language advocacy is "because the C standard does not explicitly allow this." C11 6.3.2.3p1 / p8

1. A pointer to void can be converted to or from a pointer to any type of object. A pointer to any type of object can be converted to a pointer to void and vice versa; the result should be compared equal to the original pointer.

8. A pointer to a function of one type can be converted to a pointer to a function of another type and vice versa; the result should be compared equal to the original pointer. If the converted pointer is used to call a function whose type is not compatible with the specified type, the behavior is undefined.

Note that a function is not an object in C terminology, therefore there is nothing that would allow you to convert a function pointer to a void pointer, therefore, the behavior is undefined.

Casability to void * is a common extension. C11 J.5 General extensions 7 :

J.5.7 Function pointer cast

1. A pointer to an object or void can be cast to a pointer to a function, which allows you to call data as a function (6.5.4).

2. A pointer to a function can be cast to a pointer to an object or canceled, which allows you to check or change a function (for example, by a debugger) (6.5.4).

This is required, for example, for POSIX - in POSIX there is a dlsym function that returns void * but in fact it returns either a pointer to a function or a pointer to an object, depending on the type of character allowed.


As to why this happens, nothing in the C standard is uncertain or indefinite if implementations can coordinate this. However, there were and are platforms where the assumption that the void pointer and the function pointer will have the same width will really complicate the situation. One of them is the 8086 16-bit real mode.


And then what to use instead? You can still cast any function pointer to another function pointer, so you can use the universal function pointer void (*)(void) everywhere. If you need both void * and a function pointer, you should use a structure or union or select void * to point to a function pointer, or make sure that your code only works on platforms where J.5.7 is implemented;)

void (*)() recommended by some sources, but now it seems that a warning is issued in the latest GCC because it does not have a prototype.

+2
source share

With some changes, you can avoid talking to pointers:

 #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"}; voidfunc find_ptr(char *name, char *names[], voidfunc 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, ptr_list, sizeof(ptr_list) / sizeof(ptr_list[0])); fptr(); return 0; } 
+1
source share

I answer this old question because it seems that one of the possible solutions is missing from the existing answers.

The reason the compiler forbids conversion is because sizeof(void(*)(void)) may be different from sizeof(void*) . We can make the function more general so that it can handle records of any size:

 void *find_item(char *name, char *names[], void *items, int item_size, int item_count) { int i; for (i = 0; i < item_count; i++) { if (strcmp(name, names[i]) == 0) { return (char*)items + i * item_size; } } return NULL; } int main() { voidfunc fptr; fptr = *(voidfunc*)find_item("quux", name_list, ptr_list, sizeof(ptr_list[0]), sizeof(ptr_list) / sizeof(ptr_list[0])); fptr(); return 0; } 

Now find_entry() does not need to directly process the element at all. Instead, it simply returns a pointer to an array, and the caller can cast it to a pointer to funcpointer before dereferencing it.

(The above code snippet assumes definitions from the original question. You can also see the full code here: try it online! )

0
source share

All Articles