← Back to context

Comment by amiga386

5 hours ago

Can anyone explain why this is undefined behaviour? UBSan calls it "indirect call of a function through a function pointer of the wrong type"

    struct foo {int i;};
    int func(struct foo *x) {return x->i;}
    int main() {
        int (*funcptr)(void*) = (int (*)(void*)) &func;
        struct foo foo = { 42 };
        return funcptr(&foo);
    }

While this is all kosher per the language lawyers:

    struct foo {int i;};
    int func(void *x) {return ((struct foo *)x)->i;}
    int main() {
        int (*funcptr)(void*) = &func;
        struct foo foo = { 42 };
        return funcptr(&foo);
    }

C23 §6.5.2.2p7

> If the function is defined with a type that is not compatible with the type (of the expression) pointed to by the expression that denotes the called function, the behavior is undefined.

Compatible types requires integrating texts from several different paragraphs, but the general notion is "identical type, in a frontend sense", not "same ABI." This means that "const void " and "void " are not compatible types, much less "void " and "struct foo ".

It's undefined behavior due to the "strict aliasing" rule. You're simply not allowed to cast one pointer type to another (ever!) except for the following exceptions:

- casting an object pointer to or from void*

- casting an object pointer to or from char*

You're not doing either of those things. A function pointer is not an object pointer (the standard does not guarantee that the two kinds of pointer even have the same size/representation, and in fact on some esoteric hardware they don't), and even if it were, you aren't casting to or from void* or char*. So it's UB for two separate reasons.

  • Sorry, this explanation is plain wrong.

    You can cast between pointer types freely so long as they can be representable in one another (some casts are undefined because the address would be unaligned in the target pointer type, and there's actually no guarantee that pointers to objects and pointers to functions have the same representation).

    Strict aliasing rules don't kick in at pointer type casting, but rather kick in at lvalue access--when you dereference a pointer, in other words--and you've also given the list of strict aliasing rules completely incorrectly.

Two function pointer (in practice) compatible or not depends on machine specific calling convention.

I guess enumerating all the possibility is just .. don't look right? make the standard too long and complex?

Casting to a pointer of incompatible type is UB. The exception is casting to char*.

  • Tell me why struct* is incompatible with void* when it's such a standard case in C that you don't need a cast:

        struct foo *x = malloc(sizeof(struct foo)); /* malloc returns void* */
    

    Or rather, tell me why the C11 standards committee decided to declare that struct* is incompatible with a void*

    • ok so Claude says I was wrong, it's more subtle.

      (1) you can cast between any pointer types (no UB - assuming they're aligned), but accessing memory through a wrongly-typed pointer is UB

      (2) the only exception is char*, which allows you a "byte view of memory"

      (3) calling a function through a pointer requires the parameter pointer types to be compatible, and none of these are: int*, struct foo *, void*, char*