3 ms·
From the C standard §3.4.3: undefined behavior behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for wh
by Karellen 4y ago
From the C standard §3.4.3:
undefined behavior
behavior, upon use of a nonportable or erroneous program construct or of erroneous data,
for which this International Standard imposes no requirements
NOTE Possible undefined behavior ranges from ignoring the situation completely with unpredictable
results, to behaving during translation or program execution in a documented manner characteristic of the
environment (with or without the issuance of a diagnostic message), to terminating a translation or
execution (with the issuance of a diagnostic message).
EXAMPLE An example of undefined behavior is the behavior on integer overflow
https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf
The important wording here is "this International Standard imposes no requirements"
i.e. an implementation is allowed to do literally anything in the case of undefined behaviour. It's not quite that the compiler writers are saying "this can never happen", it's more along the lines of "if this does happen, we can do anything at all, including acting as if the conditions were such that UB could not have happened."
So if you multiply two signed ints that the compiler knows are positive, the compiler can assume that the result can't overflow. Because, if it does overflow, the compiler can emit code that does absolutely anything in that case - including acting as if it didn't overflow. Therefore, it can elide checks for a negative result, because either there was no overflow in which case the check is redundant, or there was but the code is allowed to do anything at all - including not performing the check for a negative result.
- uecker 4y agoI agree with this logic. But note there is one caveat: Observable behavior that has already happened when the condition for UB is encountered can then not be affected. The C++ committee later clarified that for UB also the previous observable behavior can be affected. It is unclear whether this affects C as the C committee never added this clarification, but compiler writers often apply this interpretation to C as well. In my opinion the C++ made a mistake here, as this makes UB more dangerous.
- andrewaylett 4y agoWhen do you consider that "the condition for UB" would be encountered, for any particular behaviour that lacks a definition? The optimiser is -- in general -- allowed to re-order operations and restructure code if it doesn't change the behaviour of the program. In doing this, it needs to trust the programmer that the program is valid. Otherwise it can't even re-order two signed additions, lest the first one overflow. You might want a language that restricts this, but C is not that language. Or you might want a language that defines all its behaviours, but C is not that language either. Compiler writers put a lot of effort into making their compilers do exactly what the programmer tells them to do. My personal take is that the correct response to the difficulties of ensuring your C program doesn't exhibit undefined behaviour is probably to avoid writing new code in C. But if you do still need to write C for whatever reason (which I do, occasionally) then it's only sensible to take as much care as the language design expects programmers to take: the compiler trusts the programmer to only attempt operations with defined results.
- uecker 4y agoThe condition is always stated in the C standard. "if ... the behavior is undefined". An optimizer is not in general allowed to re-order operations. It is allowed to do this only if it can prove that there is no change in observable behavior.
- andrewaylett 4y agoIndeed, but "no change in observable behaviour" -- along with every other suggestion of correctness from a C compiler -- is only guaranteed in the presence of a well-defined program. Honestly, I think we'd all be better served by pushing the concept of "undefined behaviour" a bit further into the background. C has defined behaviours, and the standard helpfully makes explicit which behaviours fall outside the definitions. If you want a defined output then your program had better have a defined behaviour when presented with your input. I'm not suggesting this is ideal -- far from it, I avoid writing new C code. But it's what C does. If you want to avoid needing to make sure that your program only attempts defined operations, switch to a language that doesn't impose that requirement.
- gpm 4y agoThis assumes that my computer isn't allowed to be a time machine. I don't see that in the spec anywhere.
- uecker 4y agoEvery technical text needs to be read using some common sense. Once you give this away, you can justify everything.
- gpm 4y agoSure, but the common sense I (and I think I can safely say the compiler writers) are applying is "when the spec says 'the program might do anything', then there is no meaningful difference to the user whether or not we guarantee that everything up to that point was executed correctly". Who cares whether we transferred money from account A to account B when the program is then going to transfer 5 times as much from account B to account A and gift our competitor half of our money while it's at it. I'm not sure if I agree with your interpretation of the spec, but even if that's the technically correct interpretation, arguing that things went wrong because the compiler miscompiled the program and that it didn't do the things it was supposed to before it was allowed to do literally anything... just isn't an interesting argument. Things went wrong because your program was wrong.
- uecker 4y agoThe spec says there are no restriction on the behavior. But now going on saying that when there are no restriction on behavior the term "behavior" now includes impossible things like time travel or magic instead of something any actual machine could possible do, this seems far fetched to me.
- uecker 4y agoRegarding the second point. Sure the program went wrong because it was wrong. But the damage it can do when something went wrong when this can affect previous behavior is much higher. Being able to prove partial correctness of a program is a useful feature (e.g. when a transaction completed correctly you can be sure that and error in the logging function afterwards does not undo this).