Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

You have to use memcpy with length 4 to read that uint32_t from something that was a char pointer, those casts are not allowed anymore. On x86 it would become a single mov instruction.


Are you sure about this?

I did this recently in C++ and it worked fine: cast a uint8_t* to a uint32_t* and read data as 32 bit ints. I had to use reinterpret_cast in C++ though and of course I had to swap words to get the correct ordering.


Pretty sure it's UB if the uint32_t* doesn't point to a correctly-aligned address. (IIRC, just computing the unaligned pointer is already UB before you dereference it.) On x86, the code might appear to work, though, if you don't happen to trigger autovectorization.

The correct way to do an unaligned read is memcpy.


To give an example, iirc on sparc, unaligned access traps. And iirc, on older arm (before armv7), it would read at the clamped aligned address, and rotate the bits to match the unaligned address (eg if your aligned data is abcdef and you read 4 bytes at offset 2, you actually read cdab). I think you can still get this behavior on armv7 if you want.


I think even if you happen to know that the address is always aligned, but the compiler can't tell that it's guaranteed to be always aligned, the compiler can miscompile the code. Even without autovectorization.


If it's correctly aligned it will always work. The compiler must assume that your cast declares the proper type and behave accordingly. This is fundamental to C and (AFAIU) C++. The cast is the final word on the matter, but it's up to you to ensure the cast is telling the truth, the whole truth, and nothing but the truth.

What the compiler cannot glean from casts are alias relationships, which are important for ensuring the order of operations are correctly maintained. As long as you're not mutating aliased objects, it's irrelevant. But if you are mutating aliased objects, you usually should derive the alias through a union with an in-scope definition. That way the compiler, by backtracking the derivation of the reference through the union, will recognize that the two objects of different types alias and ensure that loads and stores are properly ordered.

Another way to signal alias relationships relevant to UTF-8 decoding is through pointers to char or unsigned char. Those types can legally alias anything, and the compiler must take note. Sometimes it's useful to cast an address to, e.g., (char *) even when using memcpy(), even though it's unnecessary in C for the immediate expression because of automatic void pointer conversions. I can't think of a concrete example at the moment to explain the point, but it's something to keep in mind. Note that int8_t and uint8_t are not necessarily the same type as char or unsigned char, even when they have the same representation, and thus aren't required to express to the compiler the possibility of aliasing with other types (that is, mutating a uint8_t object doesn't necessarily tell the compiler that another object of type uint32_t might have been mutated, while mutating an unsigned char object would have in the same context).


Oh in that case I understand. I'm doing pointer arithmetic on guaranteed aligned data so it compiles/works fine.


Yes, I'm sure. Here is a blog post by one of the leading professors of undefined behavior about this:

https://blog.regehr.org/archives/959

(edited to better blog post by same author)


AFAIK, and also in the examples shown with that blog post, all the problems with strict aliasing involve reading/writing the same object with separate differently-typed pointers. In my example, I'm only accessing s as a uint32_t*, so there is nothing to "alias" per se.


But `s` is defined somehow, and it is not defined as a `uint32_t * ` (or else you would not need the cast). It is usually defined as `char * `. The memory being read is reachable by dereferencing `s`, a `char * `, and by dereferencing `(uint32_t * )s`, a `uint32_t * `. Thus, type punning and aliasing. Each byte of memory should be reachable only by pointers/array indexes of a single type (and anything that is not a `char * ` can be accessed by `char * `).


That works fine if and only if uint8_t is an alias of char, as otherwise it violates the strict aliasing rule.


cast != read.

Deferencing a pointer of incorrect alignment will generate traps on many architectures.


Again, the pointers are to addresses that are guaranteed to be aligned on my target architecture.

Nonetheless, I have taken the advice in this thread and converted my code to use std::memcpy instead.


He is correct. It is undefined behavior—memcpy should be used.

C++11 § 5.2.10, paragraph 7 says:

An object pointer can be explicitly converted to an object pointer of a different type. When a prvalue v of type “pointer to T1” is converted to the type “pointer to cv T2”, the result is static_cast<cv T2>(static_cast<cv void>(v)) if both T1 and T2 are standard-layout types (3.9) and the alignment requirements of T2 are no stricter than those of T1, or if either type is void. Converting a prvalue of type “pointer to T1” to the type “pointer to T2” (where T1 and T2 are object types and where the alignment requirements of T2 are no stricter than those of T1) and back to its original type yields the original pointer value. The result of any other such pointer conversion is unspecified


> those casts are not allowed anymore

As far as I know, aliasing rules are nothing but new. You can cast _to_ char, but that's pretty the sole cast allowed in C [note: void is a kind of neutral, transitional type, and is not a cast per se]




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: