简体   繁体   中英

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.

The language lawyering reason is "because C standard does not explicitly allow it." C11 6.3.2.3p1/p8

1. A pointer to void may be converted to or from a pointer to any object type. A pointer to any object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

8. 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. If a converted pointer is used to call a function whose type is not compatible with the referenced type, the behavior is undefined.

Notice that a function is not an object in C terminology, hence there is nothing that allows you to convert a pointer to a function to a pointer to void, hence the behaviour is undefined .

Castability to void * is a common extension though. C11 J.5 Common extensions 7 :

J.5.7 Function pointer casts

1. A pointer to an object or to void may be cast to a pointer to a function, allowing data to be invoked as a function (6.5.4).

2. A pointer to a function may be cast to a pointer to an object or to void, allowing a function to be inspected or modified (for example, by a debugger) (6.5.4).

This is required by for example POSIX - POSIX has a function dlsym that returns void * but in fact it returns either a pointer to a function or a pointer to an object, depending of the type of the symbol resolved.


As to why this happens - nothing in C standard is undefined or unspecified if the implementations could agree on it. However there were and are platforms where the assumption that a void pointer and function pointer would be of the same width would really make things difficult. One of these is the 8086 16-bit real mode.


And what to use instead then? You can still cast any function pointer to another function pointer, so you can use a generic function pointer void (*)(void) everywhere. If you need both void * and a function pointer, you must use a struct or union or allocate void * to point to the function pointer, or ensure that your code only runs on platforms where J.5.7 is implemented ;)

void (*)() is recommended by some sources too, but right now it seems to trigger a warning in latest GCCs because it doesn't have a prototype.

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.

With some modification you can avoid pointer conversations:

#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;
}

As pointed out in other answers you shouldn't be allowed to assign a function pointer to an object pointer such as a void*. But you can safely assign a function pointer to any function pointer. Use reinterpret_cast in C++ .

Let me give an example:

typedef void(*pFun)(void);
double increase(double a){return a+1.0;}

pFun ptrToFunc = reinterpret_cast<void(*)(void)>(increase);

the plain

pFun ptrToFunc = increase;

doesn't compile on several compilers.

I'm answering this old question because it seems that one possible solution is missing from existing answers.

The reason why the compiler forbids the conversion is that sizeof(void(*)(void)) can be different than sizeof(void*) . We can make the function more generic, so that it can handle entries 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 the find_entry() function doesn't need to directly handle the item at all. Instead it just returns a pointer to the array, and the caller can cast it to a pointer-to-funcpointer before dereferencing it.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM