9 ms·
I have become convinced that the current screw-the-programmer interpretation of ‘undefined behaviour’ was not intended, or even imagined, by the original ANSI C
by kps 9y ago
I have become convinced that the current screw-the-programmer interpretation of ‘undefined behaviour’ was not intended, or even imagined, by the original ANSI C committee. Within the committee's mandate to ‘standardize existing practice’, it was simply an acknowledgement that C compilers translated straightforward C code into straightforward machine code without adding safety checks, and that simple code might — in some circumstances on some machines — do very strange things.
In the Rationale, the committee listed places where they intentionally diverged from existing practice. They considered “the most serious semantic change” to be requiring value-preserving (vs unsigned-preserving) integer promotion. They didn't mention ‘undefined behaviour' at all.
During the standards process, Dennis Ritchie described the ‘noalias’ proposal as “a license for the compiler to undertake aggressive optimizations that are completely legal by the committee's rules, but make hash of apparently safe programs”. That's exactly what ‘undefined behavior’ has turned into. If anyone had foreseen that at the time, the reaction would have been the same: “Noalias must go. This is non-negotiable.”
- _0ffh 9y agoThis, just so much this! I've been longing for a C compiler with a "sane optimizations only"-switch like forever. I'd gladly give up on the additional couple of per cent speed improvement obsessive compulsive compiler writers managed to eke out by ignoring the source codes' obvious intentions and defending it with "but technically it's undefined behaviour"!
- staticassertion 9y agoWhat would that look like? How would this prevent me from returning a pointer to a stack frame that no longer exists, just for example? Clearly undefined behavior, but C doesn't seem capable of expressing this safely, with or without the compiler's help?
- kps 9y agoIt wouldn't make it safe; it would make it so that the consequences of returning that pointer are just the same as if you had written an assembler routine returning that pointer. On all common hardware, that would be none if the pointer is never used; on most common hardware, it would be none if the pointer is never used in a call at least equally deep — but anyway, you'd get whatever the machine does, and if you don't like it, that's your problem. In pre-UB C, the ‘spirit of C’ that C89 was intended to maintain was that you get what you wrote. By contrast, modern ‘undefined behaviour’ means that if you return that pointer — even if it is never actually used — the compiler can throw away all the code leading up to that point (including external side effects) on the grounds that it “can't happen”. You get much less than what you wrote.
- bonzini 9y agoAre you sure about that? Can you point to a clause in the standard? What may certainly cause the behavior you describe, though, is creating a pointer to an out-of-bounds element of an array.
- userbinator 9y agoHow would this prevent me from returning a pointer to a stack frame that no longer exists, just for example? It doesn't, but the outcome would be predictable: the pointer will always be pointing there. I assume you mean by "stack frame that no longer exists" something like returning a pointer to a local variable; what that would do is return the address where the variable was --- the memory address still exists, so "no longer exists" is somewhat of a misconception here. What "sane optimisations" mean is that the compiler won't e.g. decide to remove all the code following any call to that function, just because you "invoked UB".
- staticassertion 9y agoIs that really such an improvement?
- _0ffh 9y agoMaybe you won't quickly find a practical and non-contrived application for this specific case, but there are plenty of others. UB breaking buffer overflow checks springs to mind.
- zodiac 9y agoThere's some discussion about that here https://blog.regehr.org/archives/1287 https://blog.regehr.org/archives/1287
- deleted 9y ago[deleted]
- pcwalton 9y agoEveryone posts a comment like this every time undefined behavior surprises someone. But the reality is that the reason why C remains alive is that compiler writers have managed to make it fast. It is not "a couple of percent": these kinds of optimizations can make an enormous difference when, for example, they're the difference between vectorizing a loop and not doing that. Compiler writers are not "obsessive compulsive". They respond to the demands of their customers. Frequently, the reason why these optimizations exist is that someone filed a bug asking "why doesn't the compiler do this seemingly-obvious optimization?" Often, the only reason these seemingly-obvious optimizations work at all is by exploiting undefined behavior.
- Gibbon1 9y agoC has been pretty fast for basically ever at this point. All these optimizations are just masturbation. Bonus RISC processors are mostly dead at this point having been replaced with pipelined superscalar machines. Which means most of these optimizations are totally pointless. Best comments I've read is one what notes that when you profile modern programs most of the code paths are dead. Yet you have intense hotpots which often turn out to be hand rolled assembly. The second is that you get real speed up carefully choosing your algorithms and memory layout.
- pjmlp 9y agoThe craziness for C optmizer tricks is in many cases plain phsycologic. As an example, it doesn't matter that it does in 5ms what it takes to do in 15ms without the optimization, if in the end anything less than 20ms doesn't have visibile outcome. Actually there is a remark from a famous compiler write (female), which I don't recal the name now, that C brough back into the stone age what the optimizers were already able to do for Algol family of languages. This is visible in PL/8 research paper, a compiler that already used compiler optimization passes in the 70's, nowadays mostly common on LLVM.
- umanwizard 9y agoCan you give some examples of optimization passes in clang/LLVM that you think are unnecessary?
- umanwizard 9y agoHow do you know it would only be a few percent? What if such a switch made your code ten times slower? Without actually reading the clang code and seeing what all the optimization passes do, I don't see any a priori reason for assuming it would be a few percent rather than 10x.
- zokier 9y ago> I have become convinced that the current screw-the-programmer interpretation of ‘undefined behaviour’ was not intended, or even imagined, by the original ANSI C committee Does it matter today what the original committee did or did not intended? I might even invoke the old adage "the road to hell is paved with good intentions"; the best would be to learn the lessons from their failures when designing new languages.
- geezerjay 9y ago> the current screw-the-programmer interpretation of ‘undefined behaviour’ I don't believe that interpretation was ever followed by anyone. In C parlance, "undefined behavior" only means that the standard does not define any specific behavior for a particular case. In other words, that particular code construct isn't valid C, but the standard doesn't mandate it should be illegal because some C implementation somewhere may have a very good reason to fill in the blank. Therefore, anyone writing code in standard C should simply not fumble around by writing code he does not know will actually do, and those who target a specific compiler implementation are required to know what the compiler actually does in that specific case (i.e., how they've defined the behavior left undefined by the standard) and proceed to know very well what they are doing. In short, "undefined behavior" never meant "screw-the-programmer". At most, clueless programmers screw themselves due to ignorance.
- amptorn 9y agoYour argument is tautologous. Any "clueless programmer" can "screw themselves due to ignorance" in any programming language. The question is how difficult it is for programmers to avoid mistakes and how aggressive the programming language is in punishing programmers for their mistakes. C is terrible by these measures.
- geezerjay 9y ago> Your argument is tautologous. Any "clueless programmer" can "screw themselves due to ignorance" in any programming language. It appears you're missing the fact that someone needs to be completely oblivious and very foolish to expect anything out of behavior which was intentionally left undefined. I mean "undefined behavior" clearly signals that no particular behavior should be expected. A programmer needs to be particularly clueless and specially incompetent to write code that has unexpected consequences and goes against the warnings specified not only in the international standard which specifies the language in detail but also in long-standing programming references. A programming language is not "aggressive" just because incompetent programmers decide to go against the very specification of the programming language to write broken code. Your argument is like claiming electricity is "aggressive" just because some idiot repeatedly sticks his fingers in an electrical outlets and in the process zaps himself.
- pcwalton 9y agoThe original ANSI C committee had no idea about modern optimization pipelines. If people had continually pushed back against undefined behavior back then, there's a good chance that by 2017 the result would have been that C would be dead, replaced by a language that allows for modern optimization techniques.
- Filligree 9y agoAnd would that be such a bad thing? How come we have languages like Rust that achieve almost the same speed while maintaining dramatically better safety, anyway?
- pcwalton 9y agoBecause type safety makes a lot of optimizations sound. C and C++ have to use undefined behavior rules to achieve a lot of optimizations that type safe languages can more easily perform. In fact, the optimization that the article is complaining about is really only a problem because C is not type safe.
- Filligree 9y agoSo that's my point. If C was those few percentage points slower, and died, then we'd have better languages sooner.
- kps 9y agoYou're the last person I'd have expected to make that sound like a bad thing. While benchmark games played a part in the modern ‘undefined behavior’, I'm not so sure that it would made much difference to adoption of the language. Consider Linus Torvald's well-known rant as a point in the opposite direction. In the universe where ‘undefined behaviour’ had been clearly specified as ‘what you write is what you get’, C might have gone to consistent use of optimization-enabling annotations, following ‘const’ (and later ‘restrict’). Along those lines, ‘volatile’ was ANSI's other big mistake, as creating it broke existing code; I now think that should have been the default, with fragile-optimizable status being explicitly indicated by extending the use of ‘register’ keyword.
- jcranmer 9y agoAFAICT, the main rationale for undefined behavior in the original instance boils down to traps: any operation that could trap is undefined. In particular, this seems to motivate why signed integer overflow is undefined as opposed to implementation-defined. It should be pretty clear why it's a good thing that potentially-trapping instructions have undefined semantics in those instances (or, perhaps more accurately, have semantics that have large leeway for when, where, and if the trap is triggered). Undefined behavior is generally nowhere near as bad in practice as it's often made out to be in practice. It's mostly a case of taking code that is manifestly wrong and making it manifest wrongly in different ways, and when you look at how and why compilers use the undefined behavior to optimize, it's hard to actually object to it. The two main counterexamples are strict aliasing (although it should be noted that most compilers will use the strict aliasing rules only if it failed to figure out aliasing by other means, so trivial things like int_var = (int)&float_var don't end up being deleted as being nonsensical) and signed integer overflow (note that wraparound semantics usually are as equally bad as undefined behavior, but making it be undefined makes it challenging to check if the operation would overflow).
- leeter 9y agoNot just traps... systems level code often relies on what is by the standard "Undefined Behavior" but is in fact well defined for that implementation, something the standard allows for very good reason. At the end of the day interacting with hardware sometimes requires doing things that "No Sane Programmer" would ever do normally. Most of undefined behavior is a nice way to say that the standard is not going to try to define something that is probably an artifact of the implementation. Compiler writers can however use and abuse the fact that there is no constraints. MSVC will prune code with undefined behavior. Clang may call the never-called (only if not static or in an anonymous namespace), etc. Undefined behavior is only undefined by the standard... in most cases it has very well defined results.. for a specific implementation and hardware set.