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

Viewed 230

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?

3 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.

6.7.5.3 p15

For two function types to be compatible, both shall specify compatible return types. Moreover, the parameter type lists, if both are present, shall agree in the number of parameters and in use of the ellipsis terminator; corresponding parameters shall have compatible types.

The questions reduces recursively to whether A* is compatible with void*.

6.7.5.1 p2

For two pointer types to be compatible, both shall be identically qualified and both shall be pointers to compatible types.

The type A is not compatible with void.

As dbush and alinsoar have pointed out, the problem is that int (*)(void *, void *) and int (*)(A *, A *) are not compatible. The way to fix this is to change the definition of comp as follows:

int comp( void *a, void *b )
{
  A *aa = a;
  A *bb = b;

  return aa->x - bb->x;
}
Related