Page 1 of 1

C: Type coercion of function pointers

Posted: Sat Jun 01, 2024 9:38 am
by lambduh
I'm working on decompiling a program to C (I don't know for sure, but so far it appears that it had originally been written in C). It has a callback system where a function can be scheduled to be run later. These are stored in an array of structs with information on when and how to call the function, and a pointer to that function. These functions always return a pointer of some kind, and always take one argument which is a pointer. However, the type of that pointer depends on the particular function.

So I thought the obvious solution was that the type in the struct is

Code: Select all

void * (*)(void *)
, and then I can create a function of the type, for example,

Code: Select all

int * (*)(int *)
because void pointers can be freely coerced back and forth between particular pointer types.

However, gcc is giving me an incompatible pointer warning for this behaviour. (it does work the way I expected, but I'd rather do it right).

What is the best way to deal with function pointers that can take in pointers of different types? Should I just be explicitly casting it? Is there a version of the C standard that does allow this coercion? (I haven't specified any particular standard or dialect on the command line) Does C allow any automatic type coercion between different function pointers?

Re: C: Type coercion of function pointers

Posted: Tue Jun 11, 2024 8:02 am
by nullplan
C allows object pointer conversion and function pointer conversion with explicit casts. Implicitly, the only conversion allowed is from void* to any other object pointer type and back again. As an extension, most implementations also allow implicit conversions from void* to function pointer type and back. POSIX requires this for dlsym() to work.

But that is it. Just because void* can be converted to any other type, doesn't mean you can freely convert any type based on void* to a similar construction based on another type. For example, you cannot implicitly convert a void** to int**. The same is true for your function pointers. In theory, all pointer types could have different representations (with the exception that void* and pointer to character type must have the same, and all pointers must be convertible to void* and back without loss of information), which the compiler is plastering over for you (same way it also implicitly converts from floating-point to integer and back).

Seeing as they're callbacks, I would probably attempt to figure out the signature of the callback functions and declare the callback pointers of that type.

Re: C: Type coercion of function pointers

Posted: Tue Jun 11, 2024 11:42 am
by lambduh
nullplan wrote:For example, you cannot implicitly convert a void** to int**.
Okay, that's new information to me, but I can see why you wouldn't want that after giving it some thought. void* has special significance that doesn't generalize to types made out of void*.
The same is true for your function pointers. In theory, all pointer types could have different representations
Right, this is the undefined behaviour I'm trying to avoid. This would be a reasonable enough assumption for me to make because the code is pretty heavily tied to a particular platform, as a hobby project I'm interested in making it as portable as possible.
Seeing as they're callbacks, I would probably attempt to figure out the signature of the callback functions and declare the callback pointers of that type.
The type of the callback function is

Code: Select all

void (*callback)(void *)
(or a pointer to a union over all the types used in the actual codebase). The callback function always takes a pointer to a struct that is used to store state across invocations of the callback, each callback has its own struct for its own needs.

The conclusion that I came up with while I was waiting for this thread to be accepted was that all the callback functions should look something like this,

Code: Select all

void callback(void* param_) {
    struct particular_callback_state const * param = param_;
    /* ... */
}
I understand now why the cast has to be explicit. When the original function is called, the caller is providing a

Code: Select all

void*
parameter, but the callee of will go looking for a

Code: Select all

struct particular_callback_state *
parameter. The series of casts is allowed, but the language doesn't guarantee that the casts will be compiled to no-ops so it has to be explicit.