Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is storing a function with different argument type to function pointer with void* argument UB?

I recently stumbled across an interesting question (at least i think it is). A little example:

Example

#include <stdio.h>

typedef struct A {
    int x;
} A;

int (*func)(void*, void*);

int comp(A* a, A* b) {
    return a->x - b->x;
}

int main() {
    func = comp;
    A a;
    A b;
    a.x = 9;
    b.x = 34;
    printf("%d > %d ? %s\n", a.x, b.x, func(&a, &b) > 0 ? "true" : "false");
}

I asked myself if the above shown is valid code but on compilation GCC threw a warning: warning: assignment from incompatible pointer type. I did some research and in one thread someone stated the above would be undefined behaviour now im curious why this is UB since void* can be casted savely to any possible other type. Is it just the standard saying "Nope thats undefined" or is there some explainable reason? All questions on StackOverflow I found state its UB but not exactly why. Maybe it has something to do how a function pointer is dereferenced internally?

like image 439
Yastanub Avatar asked Jan 27 '23 05:01

Yastanub


1 Answers

A void * can be safely converted to/from any other type, but that's not the conversion you're trying to do. You're trying to convert a int (*)(A *, A *) to a int (*)(void *, void*). Those are two very different things.

The automatic conversion of void * does not apply to the arguments in a function pointer. For two function pointers to be compatible, the number and type of the arguments must be compatible as well as the return type.

One of the reasons for this is that a void * need not have the same representation as other types of pointers. This is fine when simply converting to a void * and back which the standard explicitly allows, however it can be a problem when calling a function.

Suppose a void * is represented with 8 bytes and a struct pointer is represented with 4 bytes. In your example, two 8 byte values would be pushed onto the stack but two 4 bytes values would be read from the stack as parameters in the function. This would result in invalid pointer values which would be subsequently dereferenced.

like image 150
dbush Avatar answered Feb 01 '23 22:02

dbush