0

I know this is going to end up being a very lengthy question (with many sub-questions) so please accept my apologies in advance.

This last Friday, I was optimizing a particular algorithm to use my processor's native word size to perform bit-wise shifting, rather than shifting a single byte at a time. For a 32 and 64 bit processor, it is the difference between shifting [1 vs 4] or [1 vs 8] bytes simultaneously, respectively. I thought I had sorted that issue out, but one of the comments mentioned that the proposed solution code violated the "effective type rule (strict alias rule)". Having no clue what that was, I did some Googling to enlighten myself -- and discovered it is apparently illegal for two different types to be associated with the same memory address.

Let me provide an example:

unsigned char buffer[8]; // each unsigned char is 8 bits on my system

// slow approach
for (int i = 0; i < 7; ++i)
{
  buffer[i] <<= 3; // shift each byte three bits to the left
  buffer[i] |= (buffer[i + 1] >> 3); // mask in the bits from the next byte
}
buffer[7] <<= 3;

// faster approach
uint64 *p = &buffer[0]; // uint64 is 64 bits on my system
// byte swap (*p) // my system uses little endian byte order
(*p) <<= 2; // shift all 8 bytes left three bits
// byte swap (*p) // reorder bytes back to little endian

The faster approach looks attractive, but it is apparently illegal (generates undefined behavior) because I have cast the buffer array (of type unsigned char) to the type uint64.

This realization that casting from a buffer to another type was undefined behavior came as quite a shock to me -- mostly because I've dealt with A LOT of production C and C++ code over the years that routinely casts to/from various buffers to other types. Sure you have to be mindful of issues like memory alignment, byte ordering, etc., but I've never run into "undefined behavior" that wasn't the desired behavior.

As I continued my search to learn more, the primary question on my mind was "WHY?". Why is it undefined behavior to cast from a buffer to a type? I mean, this is C and C++ we are talking about -- one primary reason I use these languages is because I need a fine level of control over memory resources (allocation, deallocation, and interpretation of byte sequences.)

Has the "strict aliasing rule" always been a part of the C and C++ languages? Based on what I have read, the answer is NO. It sounds like the rule was added at some point to enable a specific compiler optimization. Ironically, it is this optimization that is responsible for the "undefined behavior" if the "strict aliasing rules" are not followed. Furthermore, several sources (including some on Stack Overflow) have alluded to the "strict aliasing rules" for C being different than those for C++. What are the differences?

Many of the resources I've come across online discussing the "string aliasing rule" for C and C++ seem to reference assembly code generated by gcc/clang. While I use these GNU tools for personal projects on Linux, I spend most of my professional development time working in Visual Studio. Does Visual Studio even support "strict aliasing rules"? If it does, the compiler option was not leaping out at me. WinAPI itself I don't think would be 100% compliant with the "strict aliasing rules"?

There seems to be a lot of ambiguity about how to follow the strict aliasing rules. Some examples use unions to accommodate the rules, but some resources say the union approach is not valid. Other resources recommend using memcpy (which also handles the alignment issues), but then you're left praying the optimizer does the right thing at compile time (not inserting call instructions for memcpy)...I can only imagine how bad it must run in debug mode (minimal optimization)

I guess I'm really just hoping someone can shed light on this whole situation. To me, it feels like this "strict aliasing" rule is more headache than it is worth (but keep in mind I've only known it has existed for about 3 days and I'm sure I haven't considered all the angles. I'd like to hear some feedback from both those who use and don't use it. What works best and is there a uniform way of using it (that isn't compiler specific)?

I also should mention that there seems to be an allowance for char * types -- these ARE allowed to alias any type so it is legal, for example, to modify a uint64 via a char*, it's just not legal to cast a char * buffer into another type.

Doesn't memcpy use the processor's native word registers to perform copies efficiently? How does it get away with it? IIRC it is written in assembly so I'm guessing this rule wouldn't affect it?

Thanks in advance.

EDIT: Someone linked my question to another question posted to Stack Overflow. While the other question contains useful information about strict aliasing, my question contains additional questions/requests for clarification.

digitale
  • 645
  • 4
  • 13
  • 1
    Strict aliasing has been in all C and C++ standards. The union approach is valid in ISO C but not in ISO C++. The mainstream C++ compilers support union aliasing as an extension. As you say, Windows API relies on union aliasing. The standards cover user programs, not implementation internals; memcpy can "use magic" (yes that's a technical term). Yes, you can modify a `uint64_t` via `char`, but you cannot modify `char` via `uint64_t`. – M.M May 10 '16 at 05:17
  • This site has a "1 question per question" policy but your "question" contains a dozen questions and many of them are opinion-based (which is also off topic). You might be better posting on a discussion forum . – M.M May 10 '16 at 05:30

0 Answers0