I know. I think the reason that this an area that causes lots of debate is because the majority of C programmers from 20 years ago would not have thought the memcpy or unions approach was a better idea than casting and dereferencing void* . The casting approach used to "just work" unless you violated the alignment requirements on your platform, and that was a problem 20 year-ago C programmers were happy to take on.
For next time this discussion comes up, I'll try to think of a good example of when the void* approach yields easier to maintain code :-)
BTW, you can also use -fno-strict-aliasing to make the C compiler sort-of work like it did 20 years ago. I believe this is what the Linux Kernel does but I couldn't be certain that it is for exactly this reason.
If you're certain there is no violation (such as with malloc), then go ahead and do it. -fno-strict-aliasing is unrelated to this issue. It removes the strict aliasing assumption from the compiler (another source of potential issues if not understood), but void* (and char*) are explicitly allowed to alias to anything regardless.
I modified the code to ensure that the data is 8-byte aligned (I think I've done this correctly). It still goes "wrong". https://godbolt.org/z/Pnxnb8
In either case, adding -fno-strict-aliasing fixes it.
uint64_t *a = malloc(8);
What you're running into in the context of your example, is breaking strict aliasing.You're starting with uint32_t pointers, which then go through void pointers and end up being accessed through pointers to uint64_t. This breaks strict aliasing since void* can not be used to bypass it.
If you had started with uint64_t, you could go through void* and back to uint64_t without any issues.
And in the case of malloc, I don't know what type the data was "originally" before it returned it to me. I guess I have to treat malloc as a special case where I can cast its void* to anything safely. That would seem morally wrong.
You need to have a model of how the compiler/optimizer works. In your example, it's important to know what objects you're working with and what the effective types of these objects are. Focus on the objects, not on the pointer types that you use to access them. Normally, the effective type of an object is its declared type. Allocated objects however, have no declared type according to the standard [1]. They get their effective type on first access:
"If a value is stored into an object having no declared type through an lvalue having a type that is not a character type, then the type of the lvalue becomes the effective type of the object for that access and for subsequent accesses that do not modify the stored value."
This means that you don't care how malloc works internally (unless you're writing one), and casting/accessing its result - which is guaranteed to be properly aligned - does not break strict aliasing.
But in your example, the strict aliasing violations are crystal clear. You declare uint32_t objects, therefore the compiler knows and can track the effective types of these objects. Later, you're trying to access the values of these objects through different types which is when the strict aliasing violation occurs.
> So is the rule that it is OK to cast a pointer back to its "original" type and dereference it?
Since you're accessing the same object and there's no mismatch between its effective type and the type you're using, you're ok.
> If so, I feel this rule breaks the abstraction mechanisms C provides. eg now when I'm implementing that xor512b() function, I need to know about the invisible "original" type of the arguments.
void* is not really an abstraction and should not be used as such (or to avoid thinking about designing a proper interface). You can make use of the type system by declaring proper types and being consistent, or you can use char* as a byte-level interface. Usually, when you see void* in an interface, it's clear what the implications are and how you should proceed with it. If it's not, you're looking at bad design.
[1] http://www.iso-9899.info/n1570.html Footnote 87) Allocated objects have no declared type.