From my perspective, the asm clearly reveals that void*
uses the same object-representation as other pointers, such as int*
, so casting to and from void*
is a no-op that just keeps the compiler's type system happy.
Everything in asm is just bytes that you can do integer operations on if you want. Pointers are just integers that you can dereference. e.g. in x86-64 asm, +1
to a uintptr_t
is no different than +1
to a char*
, because C defines sizeof(char)
as 1
, and x86-64 is byte addressable so every integer increment to a pointer is a new byte. (So C implementations on x86-64 use CHAR_BIT=8).
void*
is just a type you that can hold any pointer value, but that doesn't let you do math on it with +1
or whatever. OTOH in C you don't need to cast to assign other pointer types to/from it.
All of this follows from x86-64 having a flat memory model, not seg:off or something. And that function pointers have the same representation as data pointers. On hypothetical machines (or C implementations) you might see something special for void*
.
e.g. a machine that emulated CHAR_BIT=8 on top of word-addressable memory might have sizeof(char*) > sizeof(int*)
, but void*
would have to be wide enough to hold any possible pointer and might even have a different format than either. (Having a narrow char
that you can't actually store in a thread-safe way (without a non-atomic RMW of the containing word) wouldn't be viable for C11.)
Semi-related: Does C have an equivalent of std::less from C++? talks about how C pointers might work in other hypothetical non-simple implementations. A seg:off pointer model wouldn't make void*
different from int*
though.