C compilers are generally required to allow for the possibility that an lvalue on the right side of an assignment operator might identify the same object an lvalue on the left which has the same type and is accessed in similar fashion. They are not generally required to allow for the possibility that an lvalue used on the right side of an expression might interact with one on the left side via other means. Among other things, given something like:
void doSomething(long *p, long *q, long *r)
{
*r = *p + *q;
}
a compiler could load the bottom half of *p
, add the bottom half of *q
, store the result to the bottom half of *r
, and then load the top half of *p
, add the top half *q
along with any carry, and store the result to the top half of *r
. This could malfunction if e.g. the bottom word of *r
occupied the same storage as the top word of *q
, even if code would never try to read *q
after the assignment was processed.
If a compiler has no reason to think that an assignment will modify any of the lvalues used in the source, it may perform additional optimizations. For example, if a compiler where _Bool
is one byte is given something like:
int x;
void handleAssign(char *p)
{
x += p[0]*p[1] + p[2]*p[3];
}
it would be entitled to process it as though it were written:
int temp;
void handleAssign(char *p)
{
x += p[0]*p[1]; // Using wrapping two's-complement math
x += p[2]*p[3]; // Using wrapping two's-complement math
}
which might improve efficiency on platforms that could directly add the result of the multiply to a memory operand but would otherwise have to transfer it to another register, add the result of the second multiply to that register, and then add that result to x. Such processing is separate from type-based aliasing.