4 ms·
What modern interpretation? As long as C has been standardized, undefined behaviour has meant nasal demons. There's an argument to be had that some undefined b
by cygx 11y ago
What modern interpretation? As long as C has been standardized, undefined behaviour has meant nasal demons.
There's an argument to be had that some undefined behaviour should rather be unspecified or implementation-defined, but compilers making use of it for aggressive optimization? That's by design.
Quoting another article by John Regehr:
My view is that exploiting undefined behavior can sometimes be OK if a good debugging tool is available to find the undefined behavior.
Thus -fsanitize=undefined.
- to3m 11y agoUndefined behaviour has meant undefined behaviour. That doesn't mean nasal demons. That means this: 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). Permit me to suggest that when there is a choice between making things behave in a documented manner characteristic of the environment, or something else - don't go for the something else.
- cygx 11y agothe Standard imposes no requirements on undefined behaviour. The list you quoted is informative, to give us a basic idea what to possibly expect, not a list of things we may rely upon. Rejecting translation outright aside, undefined behaviour is the strongest language the standard uses for illegal constructs. I do not see it as undefined in the sense of lacking a common definition (eg is 0 a natural number, or not) but something that is undefined the way 0/0 is undefined in maths. In a way, the criticized behaviour of aggressively opimizing compilers is a consequence of ignoring the situation completely with unpredictable results. It's the same line of reasoning that makes restrict useful, just on steroids.
- to3m 11y agoWell I submit that as a simple question of quality of implementation, implementers should try their hardest to go for the something that behaves "in a documented manner characteristic of the platform"! But instead, they seem to pick something else. And I claim that this is not helpful to their users, who would be far better served by the characteristic-of-the-platform option.
- cygx 11y agoIt's really friction between incompatible visions of C: Some want it to be a portable wrapper over assembler with predictable semantics, others want it to run as fast as possible. Like it or not, the standard leaves the doorway open for the latter group to come up with increasingly aggressive optimization, only limited by their ingenuity.
- derefr 11y agoYou'd think that'd make things clear, then: if you want a compiler to error out on trying to compile a literal 0/0, then you'd probably also want the same behavior of trying to compile a literal dereference of 0, or a literal (INT_MAX * 2), or whatever else.
- cygx 11y agoYou missed the point of the analogy: It's not about how compilers treat the expression 0/0, but how mathematicians do. If that expression turns up in your calculation, you did something you were not supposed to do and have no one else to blame.
- derefr 11y agoNo, rebutting that was exactly my point: programming is, in some ways, a friendlier version of working on mathematical proofs, in that the compiler will tell you when you "did something you were not supposed to do." Programmers expect to be able to hack away like monkeys on typewriters, and just run into a virtual wall whenever they misstep. Compilers will error out when a programmer explicitly types "0/0", and that's a good thing. It'd be a good thing in the other cases, too.
- zAy0LfpBZLC8mAC 11y agoYou are still missing the point. Yes, compilers obviously should not intentionally produce unnecessarily destructive behaviour. But that is not what is happening. What if the code contains "x/y"? Now, the compiler can try to prove that x and y can never be 0 at the same time. But what if that proof doesn't succeed? Maybe x is user input, so who knows what the user will enter at runtime? Now, the language spec could specify that the computation of 0/0 always results in a runtime error. But if it did that, there might be target architectures where the code that would need to be generated to check for that case would have a 300% performance overhead versus code that does unpredictable stuff when it gets fed 0 for both x and y, but in every other case performs a perfectly predictable division. That slowdown would be inacceptable for computation-intensive code where a 0/0 could never happen by construction, even though the compiler can't prove it. And that is why the standard simply says "it's undefined behaviour".
- derefr 11y agoHow about being build-system defined? As in, let me specify a "target behavior profile" as a switch to the compiler, which would be shorthand for a bunch of switches that define specific results for specific undefined behaviors: "-foverflow=checked" (add explicit checks) vs. "-foverflow=wrap" (do what most ISAs already do, but even on target ISAs that don't do that) vs. "-foverflow=clip" (do something weird that no target ISA does, like making short 65535 + 1 = 65535.) I would expect that a single-pass compiler like gcc would look at the toolchain's target ISA, compare it to these switches, and then generate extra shim code to adapt any cases of target-arch-does-X into code-does-Y-on-X, while letting do-X-on-X just translate directly. Basically the same way floating-point code gets compiled: architectures that support it get it, architectures that don't get a shim. Meanwhile, a two-stage compiler with an intermediate representation, like clang, should generate IR with granular types specifying the chosen behavioral semantics on each operation. The second step in such a toolchain becomes much more formalized: rather than taking code that can contain undefined behavior as input, and then "defining" it during translation in some random heuristic manner, you just take code that already specifies the behavior it wants in full, and then attempt to generate target-arch code that does whatever it says in the most efficient manner possible while not breaking any of the guarantees the IR asks for. In other words, your code-generation stage becomes a static recompiler, like this one[1] for the NES ISA. And you can always specify a switch at code-gen time that will error out the compilation if you've done something that has caused shims to be inserted. Or even a switch to throw away the behavioral semantics the IR asks for and substitute your own, like happens in most performance-focused emulator software. [1] http://andrewkelley.me/post/jamulator.html http://andrewkelley.me/post/jamulator.html
- cygx 11y agoAs long as the option -foverflow=undefined is kept around, you have my vote. Now, it's just a simple matter of programming ;)
- derefr 11y agoI think the equivalent for the current practice would basically be: 1. setting "-foverflow=promote" on the compiler's parsing-and-IR-generating stage, thus generating code that acts like all integers occupy infinitely-sized abstract machine registers, and expects this to get shimmed as checked promotion to pointers-to-structs from a bignum library on most architectures; and then, seperately, 2. setting an optimization flag like "-Onopromote" for the target-recompilation stage, that throws away all the bignum promotion shims that would have been inserted, and pretends that it's adhering to bignum-promotion semantics anyway (thus creating code that will be optimized the way C code currently is.) That's quite a bit more complicated, but also a lot more explicit about what's going on: you have to specify (or pick a profile that specifies) the actual semantics you want, and then you have to explicitly opt into assuming those semantics rather than enforcing them. There'd probably be a similar pair of {semantic, assumption} flags for each currently-undefined behavior.