EDIT2:
It seems that the standard is not exactly clear, and appears to at least partially contradict itself in different parts on the usage of void* or struct* when casting function pointers.
The lack of clarity is such that major security projects have found themselves using very similar techniques as those described in this question, only to then have LLVM release a version of UBSAN in clang 17 (2023) which interprets these techniques as UB and throws runtime warnings.
While it is a shame that there is not more clarity on such fundamental issues, I have concluded that it is not worth the potential aggravation and have resorted to "the dark arts of macro thunking". See my second self answer below
EDIT1: I learned a lot from the answers, and they are correct. The code as shown is not well defined, because void* is "not compatible" with square*, specifically because pointers to struct and non struct types may have different representations, as quoted here.
https://stackoverflow.com/a/1241314/1087626
However, as stated in above quote, and confirmed below, all pointers to struct types are guaranteed by the standard to have the same size and representation. So all I need to do, is to change void* to obj_* where obj_ is a dummy struct, and the code is supported by the standard. Full code shown in self answer below.
Original question:
In my mock example below, I am trying to construct a generic_processor interface, where the caller passes the object pointers into a void* param in the processor, and the caller explicitly casts the "actions" to void(*gfp)(void) (a generic function pointer), which is what the generic_processor expects.
The "concrete actions" have almost identical signatures for each action across the types, in that they take an object pointer, maybe some other params and return an int. Note that the concrete actions take object pointers of the concrete type.
Here is the key step, which I have doubts about: The generic_processor casts the gfp params to a function pointer typedef with a signature which matches the concrete action functions except that the object pointer is now a void*.
As quoted in this answer:
https://stackoverflow.com/a/189126/1087626
- 766 A pointer to a function of one type may be converted to a pointer to a function of another type and back again;
- 767 the result shall compare equal to the original pointer.
- 768 If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.
So, the core of the question is: What does "not compatible" mean in this context?
In the generic_processor I am casting to a function pointer signature which is the same as the original concrete signature, except that it has a void* as the first parameter, instead of circle* or square*.
Is this well defined?
Mock example:
#include <stdio.h> // types typedef struct { int a; double d; } circle; typedef struct { int b; float f; } square; // ... 10 types,.. different sizeof() // concrete API int open_circle(circle* c) { printf("opening circle: %d: %f\n", c->a, c->d); return c->a; } int open_square(square* s) { printf("opening square: %d: %f\n", s->b, s->f); return s->b; } int send_circle(circle* c, const char* msg) { printf("sending circle: %d: %f: %s\n", c->a, c->d, msg); return -c->a; } int send_square(square* s, const char* msg) { printf("sending square: %d: %f: %s\n", s->b, s->f, msg); return -s->b; } // ten more operations for each type // "genericised" function pointer types (note the void* params!!) typedef int (*open_fpt)(void* o); typedef int (*send_fpt)(void* o, const char*); typedef void (*gfp)(void); // generic function pointer int generic_processor(void* obj, gfp open, gfp send) { int sum = 0; sum += ((open_fpt)open)(obj); sum += ((send_fpt)send)(obj, "generically sent"); return sum; } int main() { circle c = {2, 22.2}; square s = {3, 33.3F}; int net = 0; net += generic_processor(&c, (gfp)open_circle, (gfp)send_circle); net += generic_processor(&s, (gfp)open_square, (gfp)send_square); printf("net %d\n", net); return 0; } compiled with gcc 13.2: works fine, no warnings:
gcc -std=c99 -g -o gfp tests/gfp.c -Wall -Wextra -pedantic -fsanitize=address,leak,undefined && ./gfp opening circle: 2: 22.200000 sending circle: 2: 22.200000: generically sent opening square: 3: 33.299999 sending square: 3: 33.299999: generically sent net 0
void*can be cast from any pointer and to any pointer? I cannot change the concrete functions. My only other solution is to have 10x10 wrapper functions which accept a void* cast it and call with a square*. In fact the compiler does not require the cast. Which is why I came up with the above. Other suggestions?