Not sure about later versions, but c89 doesn't seem to forbid casting a function pointer to a data pointer (it does say that casting pointers has implementation defined aspects, so lisp machines aren't out of spec if they fail).

The reason you can chain unlimited ''s is that f isn't actually a function pointer, it's a function designator. Function designators are automatically converted to function pointers in almost every case, but '' is special cased so that indirecting a raw function designator results in another function designator.

Edit: Actually on a closer reading, maybe it does. C89 doesn't explicitly point it out as forbidden or undefined, it simply defines two classes (objects and functions) and only defines a very limited set of valid operations on pointers to functions.

Though one operation that is implementation defined is converting a pointer to an int, and an int to a pointer. So with two steps through an implementation defined path, you have a data pointer to a function.

void* and void(*)() are just not guaranteed to be the same size. If the latter is larger then those "implementation defined aspects" are what kicks in. It's nothing to do with the int.

In some systems functions are smaller, since they're gate addresses in a jump table (who has more than 64k functions?), so that's no problem.

But in some systems functions are bigger, and PCDOS systems running in real mode this was very common, and in those cases you'd need to get the extra bits from someplace else (union trick), or just design things another way.

In 1989 compilers weren't adversarial yet, and language specs weren't expected to be complete formal descriptions. The complete formal description was "whatever the compiler does" and "whatever the machine does" which was usually something sensible, unlike today.

I don't think it's bad enough to think of them as an adversary: I think letting the compiler do a little of the boring stuff is ok, and for the most part I am rarely surprised by this stuff. I don't -O3 and -funroll-loops and just forget-about-it either - I choose each of the -f options individually based on a combination of my understanding of what kinds of transformations they do, testing, and whether I am prepared to continuously keep those things in mind when programming.

What are you thinking of?