Let’s say I have a function that accepts a void (*)(void*) function pointer for use as a callback:
void do_stuff(void (*callback_fp)(void*), void* callback_arg);
Now, if I have a function like this:
void my_callback_function(struct my_struct* arg);
Can I do this safely?
do_stuff((void (*)(void*)) &my_callback_function, NULL);
I’ve looked at this question and I’ve looked at some C standards which say you can cast to "compatible function pointers", but I cannot find a definition of what "compatible function pointer" means.
As far as the C standard is concerned, if you cast a function pointer to a function pointer of a different type and then call that, it is undefined behavior. See Annex J.2 (informative):
Section 6.3.2.3, paragraph 8 reads:
So in other words, you can cast a function pointer to a different function pointer type, cast it back again, and call it, and things will work.
The definition of compatible is somewhat complicated. It can be found in section 6.7.5.3, paragraph 15:
The rules for determining whether two types are compatible are described in section 6.2.7, and I won’t quote them here since they’re rather lengthy, but you can read them on the draft of the C99 standard (PDF).
The relevant rule here is in section 6.7.5.1, paragraph 2:
Hence, since a
void*is not compatible with astruct my_struct*, a function pointer of typevoid (*)(void*)is not compatible with a function pointer of typevoid (*)(struct my_struct*), so this casting of function pointers is technically undefined behavior.In practice, though, you can safely get away with casting function pointers in some cases. In the x86 calling convention, arguments are pushed on the stack, and all pointers are the same size (4 bytes in x86 or 8 bytes in x86_64). Calling a function pointer boils down to pushing the arguments on the stack and doing an indirect jump to the function pointer target, and there’s obviously no notion of types at the machine code level.
Things you definitely can’t do:
stdcallcalling convention (which the macrosCALLBACK,PASCAL, andWINAPIall expand to). If you pass a function pointer that uses the standard C calling convention (cdecl), badness will result.thisparameter, and if you cast a member function to a regular function, there’s nothisobject to use, and again, much badness will result.Another bad idea that might sometimes work but is also undefined behavior:
void (*)(void)to avoid*). Function pointers aren’t necessarily the same size as regular pointers, since on some architectures they might contain extra contextual information. This will probably work ok on x86, but remember that it’s undefined behavior.