When asking about common undefined behavior in C, people sometimes refer to the strict aliasing rule.
What are they talking about?
相关问题
- Multiple sockets for clients to connect to
- What is the best way to do a search in a large fil
- glDrawElements only draws half a quad
- Index of single bit in long integer (in C) [duplic
- Equivalent of std::pair in C
Technically in C++, the strict aliasing rule is probably never applicable.
Note the definition of indirection (* operator):
Also from the definition of glvalue
So in any well defined program trace, a glvalue refers to an object. So the so called strict aliasing rule doesn't apply, ever. This may not be what the designers wanted.
The best explanation I have found is by Mike Acton, Understanding Strict Aliasing. It's focused a little on PS3 development, but that's basically just GCC.
From the article:
So basically if you have an
int*
pointing to some memory containing anint
and then you point afloat*
to that memory and use it as afloat
you break the rule. If your code does not respect this, then the compiler's optimizer will most likely break your code.The exception to the rule is a
char*
, which is allowed to point to any type.After reading many of the answers, I feel the need to add something:
Strict aliasing (which I'll describe in a bit) is important because:
Memory access can be expensive (performance wise), which is why data is manipulated in CPU registers before being written back to the physical memory.
If data in two different CPU registers will be written to the same memory space, we can't predict which data will "survive" when we code in C.
In assembly, where we code the loading and unloading of CPU registers manually, we will know which data remains intact. But C (thankfully) abstracts this detail away.
Since two pointers can point to the same location in the memory, this could result in complex code that handles possible collisions.
This extra code is slow and hurts performance since it performs extra memory read / write operations which are both slower and (possibly) unnecessary.
The Strict aliasing rule allows us to avoid redundant machine code in cases in which it should be safe to assume that two pointers don't point to the same memory block (see also the
restrict
keyword).The Strict aliasing states it's safe to assume that pointers to different types point to different locations in the memory.
If a compiler notices that two pointers point to different types (for example, an
int *
and afloat *
), it will assume the memory address is different and it will not protect against memory address collisions, resulting in faster machine code.For example:
Lets assume the following function:
In order to handle the case in which
a == b
(both pointers point to the same memory), we need to order and test the way we load data from the memory to the CPU registers, so the code might end up like this:load
a
andb
from memory.add
a
tob
.save
b
and reloada
.(save from CPU register to the memory and load from the memory to the CPU register).
add
b
toa
.save
a
(from the CPU register) to the memory.Step 3 is very slow because it needs to access the physical memory. However, it's required to protect against instances where
a
andb
point to the same memory address.Strict aliasing would allow us to prevent this by telling the compiler that these memory addresses are distinctly different (which, in this case, will allow even further optimization which can't be performed if the pointers share a memory address).
This can be told to the compiler in two ways, by using different types to point to. i.e.:
Using the
restrict
keyword. i.e.:Now, by satisfying the Strict Aliasing rule, step 3 can be avoided and the code will run significantly faster.
In fact, by adding the
restrict
keyword, the whole function could be optimized to:load
a
andb
from memory.add
a
tob
.save result both to
a
and tob
.This optimization couldn't have been done before, because of the possible collision (where
a
andb
would be tripled instead of doubled).Type punning via pointer casts (as opposed to using a union) is a major example of breaking strict aliasing.
A typical situation you encounter strict aliasing problems is when overlaying a struct (like a device/network msg) onto a buffer of the word size of your system (like a pointer to
uint32_t
s oruint16_t
s). When you overlay a struct onto such a buffer, or a buffer onto such a struct through pointer casting you can easily violate strict aliasing rules.So in this kind of setup, if I want to send a message to something I'd have to have two incompatible pointers pointing to the same chunk of memory. I might then naively code something like this:
The strict aliasing rule makes this setup illegal: dereferencing a pointer that aliases an object that is not of a compatible type or one of the other types allowed by C 2011 6.5 paragraph 71 is undefined behavior. Unfortunately, you can still code this way, maybe get some warnings, have it compile fine, only to have weird unexpected behavior when you run the code.
(GCC appears somewhat inconsistent in its ability to give aliasing warnings, sometimes giving us a friendly warning and sometimes not.)
To see why this behavior is undefined, we have to think about what the strict aliasing rule buys the compiler. Basically, with this rule, it doesn't have to think about inserting instructions to refresh the contents of
buff
every run of the loop. Instead, when optimizing, with some annoyingly unenforced assumptions about aliasing, it can omit those instructions, loadbuff[0]
andbuff[1
] into CPU registers once before the loop is run, and speed up the body of the loop. Before strict aliasing was introduced, the compiler had to live in a state of paranoia that the contents ofbuff
could change at anytime from anywhere by anybody. So to get an extra performance edge, and assuming most people don't type-pun pointers, the strict aliasing rule was introduced.Keep in mind, if you think the example is contrived, this might even happen if you're passing a buffer to another function doing the sending for you, if instead you have.
And rewrote our earlier loop to take advantage of this convenient function
The compiler may or may not be able to or smart enough to try to inline SendMessage and it may or may not decide to load or not load buff again. If
SendMessage
is part of another API that's compiled separately, it probably has instructions to load buff's contents. Then again, maybe you're in C++ and this is some templated header only implementation that the compiler thinks it can inline. Or maybe it's just something you wrote in your .c file for your own convenience. Anyway undefined behavior might still ensue. Even when we know some of what's happening under the hood, it's still a violation of the rule so no well defined behavior is guaranteed. So just by wrapping in a function that takes our word delimited buffer doesn't necessarily help.So how do I get around this?
Use a union. Most compilers support this without complaining about strict aliasing. This is allowed in C99 and explicitly allowed in C11.
You can disable strict aliasing in your compiler (f[no-]strict-aliasing in gcc))
You can use
char*
for aliasing instead of your system's word. The rules allow an exception forchar*
(includingsigned char
andunsigned char
). It's always assumed thatchar*
aliases other types. However this won't work the other way: there's no assumption that your struct aliases a buffer of chars.Beginner beware
This is only one potential minefield when overlaying two types onto each other. You should also learn about endianness, word alignment, and how to deal with alignment issues through packing structs correctly.
Footnote
1 The types that C 2011 6.5 7 allows an lvalue to access are: