Hacker News new | ask | show | jobs
by im3w1l 612 days ago
I think this is more of an edge case than you give it credit for.

(External linkage) functions, and their callsites, are quite special as they straddle the boundary between actual machine and virtual machine.

The calling convention makes fairly strict requirements of what that must compile down to, since caller and callee could be dynamically linked objects compiled by different compilers or even entirely different languages.

If you define a function that takes a void* parameter it will read a pointer from the RDI(?). If you pass a char* it will pass the pointer in that same register.

Now it must be said that if the compiler can see both the definition and call, then it's free to do whatever it wants e.g. inline or something.

But yeah it's a bit complicated?

1 comments

Sure, I mean, my objection is with the pretense that a reasonable calling convention in 2024 could decide to represent void* differently from any other T. It makes total sense that C programmers expect pointers to be (transitively) convertible to/from void, so the fact that they aren't convertible in this way without a trampoline means that the standard contains surprises for even very experienced developers.

I postulate that almost all UB in the wild comes from the Standard diverging from (often very reasonable) expectations, and I see that as a big problem with the standard, at least as long as compilers can't reliably detect the problem at compile-time. (And yes, C++ is even more problematic here.)

I think one of the reasons Zig exists is that it contains far fewer surprises. The reason Rust exists is that it does a much better job at preventing and containing such surprises.

Every C compiler I ever used will tell you that void()(void) is not convertible to void ()(char). If people still do it then they are a bit on their own. But how is this then different to Rust's "unsafe"? (of course, there is other UB compiler do not tell you about, that this seems a bad example)
What if the function is written in assembler and takes some pointer to some opaque memory region?

void (*)(char *) and void (*)(void *) and even void\()(struct SomeStruct*) where SomeStruct is declared but never defined could all be correct and reasonable ways to declare that function in a c-header.