29

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?

sagittarian
  • 991
  • 1
  • 10
  • 14
  • 1
    IIRC the issue is that code pointers may not necessarily be the same size as data pointers. In most architectures they are. I will leave it to someone else to find the reference to the C standard. You can probably achieve the result you want by casting via `uintptr_t` and back. – abligh Apr 15 '16 at 11:19
  • 3
    It's undefined behavior to cast function pointers to other pointer types because of the possible size mismatch mentioned by @abligh For more details see this question: http://stackoverflow.com/questions/13696918/c-cast-void-pointer-to-function-pointer – erdeszt Apr 15 '16 at 11:21
  • 3
    What keeps you from replacing `void *ptrs[]` by `voidfunc * ptrs`? – alk Apr 15 '16 at 11:25
  • 3
    Did you try a union of data pointers and function pointers? – MikeC Apr 15 '16 at 11:25
  • As you obviously do not want to call the function selected, but just return it, you want to solve this by an indirection, probably passing an array of indexes, referring to the entries of the array you are using for this current and unsolvable approach. – alk Apr 15 '16 at 11:33
  • Having two parallel arrays with related information at the same index in the respective array is so 70s, i.e. BCPL! C introduced structures; I would put a string (key) and the properly typed function pointer (value) in a struct and have a *single* array of those structs. Sometime in the late 80s people started using associative arrays or dictionaries for that (in C++ the lookup and call would boil down to something like `funcmap["quux"]()`). This needs strong types; for functions with various signatures that can be difficult. – Peter - Reinstate Monica Aug 27 '16 at 00:50
  • Note the Python API [literally requires one](https://docs.python.org/3/c-api/type.html#c.PyType_Slot.PyType_Slot.pfunc) to store function pointers as `void *`. So in practice, it works. – alexchandel Aug 16 '23 at 22:40

7 Answers7

34

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.

Art
  • 19,807
  • 1
  • 34
  • 60
  • 2
    Great answer, but I have one question: Is it safe to store pointer to a (any) function inside of `void (*f)()` and cast it to a proper type when making a call? – HolyBlackCat Apr 15 '16 at 12:45
  • 5
    @HolyBlackCat From what I know about architectures that exist today, yes. From the point of view of the standard, no. From the point of view of future compatibility, I'd rather not. From the point of view of what company you'll be in when it breaks, go for it. A good way to see what you can get away with is to look at large projects. If for example the Linux kernel breaks when compilers get strict about something you can be pretty sure you'll hear about it and at least there'll be flags to disable it. Same goes for hardware architectures, few will build a CPU today that Linux can't run on. – Art Apr 15 '16 at 13:12
  • 4
    [This answer](http://stackoverflow.com/a/5579907/149138) contradicts yours. In particular, it quotes the standard which indicates that function pointers can be cast to any other function pointer type and back again. – BeeOnRope Mar 07 '17 at 23:01
  • @BeeOnRope It doesn't contradict. I just made a little bit too hard restriction (in parentheses). Edited it out since it wasn't too relevant. And clarified what I mean with pointers to functions returning a value being cast to void functions. – Art Mar 08 '17 at 07:26
  • 1
    Right well I meant it contradicts that one part about casting function pointers. If you could cast them at all it would be problematic, but as it is you can use `void (*)()` as kind of a void pointer for function pointers. – BeeOnRope Mar 08 '17 at 13:28
  • Is it `void (*)()` or `void* (*)()` – technosaurus Aug 27 '18 at 17:08
  • 1
    @technosaurus `void (*)()` is a pointer to a function taking unspecified arguments and returning void. – Antti Haapala -- Слава Україні Oct 28 '18 at 07:02
  • 1
    @AnttiHaapala That was my point. `void (*)()` only works for functions that return void. `void* (*)()` works for a larger range of function return types. – technosaurus Oct 28 '18 at 18:52
  • 2
    @technosaurus incorrect-o. You must always back-cast to the correct function-pointer type before calling it, no matter "how close" it looks. – Antti Haapala -- Слава Україні Oct 28 '18 at 19:04
  • If the ia64 ABI mandates that function pointers are incompatible with object pointers, wouldn't that make ia64 ABI compliance and POSIX compliance incompatible requirements (as POSIX mandates that object pointers and functions pointers are compatible, as may be seen with functions such as dlsym)? Seems weird to me tbh – Gabriel Ravier Oct 24 '19 at 05:41
  • Booooooooooooooo!!! Who are you to tell me what I should and shouldn't cast to `void*`? – Andrew Apr 30 '22 at 01:09
  • Making it a hard error would literally break the Python API, which [literally requires you](https://docs.python.org/3/c-api/type.html#c.PyType_Slot.PyType_Slot.pfunc) to store function pointers as `void *`. You can moan all you want about it, but real-world code is full of it. – alexchandel Aug 16 '23 at 22:39
15

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.

q-l-p
  • 4,304
  • 3
  • 16
  • 36
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
11

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.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • 2
    "*This is something that has long been broken in the C standard and has never been fixed*" is slightly inaccurate. This is a hardware standardisation limitation, not a language standard limitation. – user426 Nov 18 '21 at 09:28
  • Can you please refer the C11 where the "initialization between function pointer and 'void *'" is forbidden? – pmor Jan 10 '22 at 13:29
  • 3
    @pmor: 6.3.2.3 speficially lists out all the pointer conversions that are legal -- conversions between function pointers and object pointers are not listed, so are undefined. – Chris Dodd Jan 10 '22 at 16:44
11

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.

xuiqzy
  • 179
  • 2
  • 14
  • 8086 16-bit mode has the issue of two different sizes of pointers, but the two sizes apply to both object pointers and function pointers equally. So for compilers that use `near` and `far` pointer qualifiers, a `near` object pointer is the same width as a `near` function pointer while a `far` pointer is similarly always the same width (though different from a `near`) – Chris Dodd Jan 10 '22 at 16:56
  • @ChrisDodd Turbo C for example had several *memory models*, ***medium** memory model* meant that all object pointers were `near`, and code pointers (functions) were `far`, and the opposite model **compact** meant that all object pointers were `far` and function pointers `near`. – Antti Haapala -- Слава Україні Jan 11 '22 at 21:05
2

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;
}
Aivars
  • 93
  • 7
  • I don't want to declare the pointer array parameter as function pointers because I use the same function for other things too. – sagittarian Apr 19 '16 at 12:05
2

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.

jimifiki
  • 5,377
  • 2
  • 34
  • 60
  • Why not use a suggest a simpler C style cast, especially given this is a C question ? `pFun ptrToFunc = (void(*)(void))increase;`? – user426 Nov 18 '21 at 09:32
1

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!)

jpa
  • 10,351
  • 1
  • 28
  • 45
  • 1
    *The reason why the compiler forbids the conversion is that `sizeof(void(*)(void))` can be different than `sizeof(void*)`* That is **one** reason. That is not **the** reason. Read [@Art's answer to this very question](https://stackoverflow.com/a/36646099/4756299) for **another** reason. Not only that, this answer is confusing at best, and perhaps even a strict aliasing violation, and certainly incomplete. What is `voidfunc`? How is the list populated so that your `find_item()` will work? And `*(voidfunc*)find_item` sure looks like a potential strict aliasing violation. – Andrew Henle Oct 28 '18 at 12:02
  • 1
    @AndrewHenle This isn't actually casting a function pointer anywhere. It is casting a pointer to a function pointer, and the function pointers themselves are just data. As far as I know, pointer arithmetic using void* and char* doesn't violate strict aliasing in any way. They aren't even dereferenced here. I felt like it wouldn't make sense to copy-paste all of the code from the original question when I only modify one function - you can find the typedef for `voidfunc` etc. in the question. – jpa Oct 28 '18 at 15:51
  • *It is casting a pointer to a function pointer* You can not do that in strictly-conforming C code. Read [6.3.2.3 Pointers](https://port70.net/~nsz/c/c11/n1570.html#6.3.2.3). What you are proposing is not covered by *any* of the eight paragraphs there, and is therefore beyond the scope of the standard C language. – Andrew Henle Oct 28 '18 at 16:22
  • @AndrewHenle You raise a valid point; I don't see why the standard doesn't seem to allow this, so I've asked it as a separate question https://stackoverflow.com/questions/53033813/can-you-cast-a-pointer-to-a-function-pointer . I guess it boils down to whether a function pointer is "object type" or not (a function itself certainly isn't an object, but the pointer might be). – jpa Oct 28 '18 at 16:44
  • @AndrewHenle "*perhaps even a strict aliasing violation*". C : "*hey, here's some strict aliasing rules, UB if you violate them, perf reasons.*". Dev : "*Hey compiler, you'll error out on violation right ?*". Compiler : "...". Dev : "*...Right ???*". – user426 Nov 18 '21 at 09:35
  • @AndrewHenle "It is casting a pointer to a function pointer" is ambiguous. Is "to" the preposition associated with "casting" or with "pointer". You assumed the former, while in context the latter was meant, and is strictly conformant. Reworded slightly to remove the ambiguity: "There exists a pointer to a function pointer. I will cast it." – Ben Voigt Feb 28 '22 at 23:02