3 ms·
The second guide explains some cases for undefined behavior, but it mixes up some things (or explains them poorly)... For example, I understand why signed inte
by tomp 11y ago
The second guide explains some cases for undefined behavior, but it mixes up some things (or explains them poorly)...
For example, I understand why signed integer overflow and violating type rules are UB; the first allows the compiler to optimize many inequalities, while the second enables various optimizations that rely on alias analysis.
But I can't understand why e.g. use of an uninitialized variable or oversized shift amounts are UB. Unless these also enable some powerful optimizations (the article doesn't mention any, and I can't think of any either), it would be much better to leave them implementation defined, not undefined. In that case, reading an uninitialized variable could result in any value, but it would still make the whole program valid.
Edit: should have said "platform-defined" instead of "implementation-defined".
- bluetomcat 11y agoOn some architectures, an uninitialized value may contain either a trap representation or a valid value, so enforcing any consistent implementation defined behavior seems impossible.
- tomp 11y agoReally? I didn't know that. Which architectures are that (that contain a trap)? Still, crashing would be a valid "implementation-defined" behaviour in my book.
- bluetomcat 11y agox86 has signalling floating-point NaNs, for example. This means that any attempt to access an uninitialized floating-point value could potentially crash the program (or at least affect the control flow).
- tomp 11y agoHm... according to the example at http://en.cppreference.com/w/cpp/types/numeric_limits/signaling_NaN http://en.cppreference.com/w/cpp/types/numeric_limits/signal... sNaN doesn't actually trap - it just records a special bit in a FPU exception register. So yes, it can affect the control flow - which is something you should expect reading aribtrary values!
- pdw 11y agoOn Itanium, each register contains a trap bit. Any uninitialized value might be a trap representation on that architecture. https://blogs.msdn.microsoft.com/oldnewthing/20040119-00/?p=41003/ https://blogs.msdn.microsoft.com/oldnewthing/20040119-00/?p=...
- josephlord 11y agoSurely undefined behaviour in a language specification is by definition implementation defined? Now the definition could vary between doing something that seems obviously correct, crashing or even deleting every file on your disk but whatever it does is up to the compiler implementer.
- tomp 11y agoYou're right, I should have been more specific. By "implementation defined", I actually meant "platform-defined", not "compiler-implementation defined". So, basically, only the native code emitter would take these behaviors into account, and could do whatever (e.g. emulate, crash, emit instructions and hope for the best, ...). But the rest of the compiler pipeline (optimisations) would still need to treat the code as valid.
- pdw 11y ago"Implementation defined" has a specific meaning in the context of the C standard: it means that an implementation needs to define and document a particular behavior. E.g. for GCC: https://gcc.gnu.org/onlinedocs/gcc/C-Implementation.html https://gcc.gnu.org/onlinedocs/gcc/C-Implementation.html For undefined behavior, the standard makes no requirements at all on the compiler.
- legulere 11y ago> I can't understand why e.g. use of an uninitialized variable or oversized shift amounts are UB Probably some obscure architecture traps on oversized shifts or something similar. To correctly implement it you would first have to check if the shift lies inside the bounds with a branch. Branches are expensive - this is the powerful optimisation.
- tomp 11y agoIf I write if ((1 << 33) == 0) { // whatever } I'm fine with my program crashing. I'm not fine with my compiler simply assuming this cannot happen, and removing the whole `if` statement.
- chrisseaton 11y agoRight but are you also fine if your program runs slower to achieve that? Because that's the trade off in many cases. Slowing programs down with bounds and aliasing checks is not the C philosophy.
- lmm 11y ago> I'm fine with my program crashing. I'm not fine with my compiler simply assuming this cannot happen, and removing the whole `if` statement. What's the difference? Do you mean you don't want your compiler to ever reorder instructions? That would cost a lot of performance. Try to formalize the guarantee that you want the compiler to give in the presence of programs like that, and the reasons for leaving it undefined become a lot more clear.
- tomp 11y ago> Do you mean you don't want your compiler to ever reorder instructions? The compiler's job is making code run faster while preserving intent. If I can write a 33bit shift in ASM, then I should be able to write it in C. How the CPU executes the code is my (the programmer's) business, not the compiler's.
- Kristine1975 11y ago>But I can't understand why e.g. use of an oversized shift amounts are UB Suppose a CPU, when shifting a 32 bit register, uses only the lower 5 bits of the shift count. Then shifting a register by 32 will be a shift by 0, i.e. a no-op (instead of clearing the register). The compiler would have to generate additional code to correctly handle this which would be slower. So it was decided to make oversized shifts UB.
- tomp 11y ago> So it was decided to make oversized shifts UB. Exactly the stupidity I was pointing out - why make it undefined behaviour, when you could make it implementation-defined behaviour. You might argue the effects are the same, but they aren't, of course - the first is global (the whole program is invalid), whereas the second is local (only that instruction has an unknown (but probably still deterministic) outcome, and only in specific circumstances).