The C abstract machine defines the behavior, and optimization passes aren't allowed to change the behavior.
-ffast-math is the option that allows incorrect semantics (e.g commutative floating point addition), but compilers have a desire to do incorrect things for "performance".
I would hope it is obvious that changing behavior for performance isn't ok, but lets say it was. A compiler could change
y=1.0/fsqrt(x)
from (v. approximate, untested, and written in a text box :D)
movsd xmm0, $(wherever x is) sqrtsd xmm0 movsd xmm1, $(location of 1.0 constant) divsd xmm1, xmm0 movsd $(addr of y), xmm1
to something like
movsd xmm0, $(wherever x is) rsqrtsd xmm0 movsd $(addr of y), xmm0
This would be much much faster, and vastly incorrect (IIRC rsqrt is only 12bits of precision)
Or even more dumb, remove calls to printf, because printf is slow.
A compiler does not have absolute leeway in all things, it is still restricted to ensuring that observable results of the C abstract machine are correct.
Python, for example:
>>> 8 is 8
True
>>> 8**1000 is 8**1000
False >>> [] == False
False
>>> object() == False
False
>>> "" == False
False
>>> {} == False
False
They're just different.
As an aside, if your concern is that the standards impose too much restrictions, another popular option is the ffast-math option.
We see this come up with strings fairly often in higher level languages where, sometimes, logical equal strings will happen to be equal because the runtime's interning system made them point to the same location, but there is no guarantee this would happen and the implementation is free to change it at any point. I believe I have also seen this behaviour come up with interning on some Integer wrapper types.