10 ms·
The author claims that: > In C89, undefined behavior is interpreted as, “The C standard doesn’t have requirements for the behavior, so you must define what the
by _kst_ 7y ago
The author claims that:
> In C89, undefined behavior is interpreted as, “The C standard doesn’t have requirements for the behavior, so you must define what the behavior is in your implementation, and there are a few permissible options”.
That's a serious misinterpretation. C89 says that "Permissible undefined behavior" includes "ignoring the situation completely with unpredictable results". There is absolutely no requirement to document what those "unpredictable results" might be.
The standard joke is that one possible consequence of undefined behavior is making demons fly out of your nose -- not because that's actually possible, but because actually making demons fly out of your nose would not violate the standard. That's equally true in C89 and all later versions of C. The change in C99 from "Permissible" to "Possible" made no difference. The phrase "imposes no requirements" has always meant exactly that. (And if you think the change from "Permissible" to "Posisble" is semantically significant, then it would have been a recognition of what was already accepted.)
The idea of "nasal demons" goes back to 1992. http://catb.org/jargon/html/N/nasal-demons.html http://catb.org/jargon/html/N/nasal-demons.html
- mjd 7y agoI came here to say something similar. The author says: > In C89, undefined behavior is interpreted as, “The C standard doesn’t have requirements for the behavior, so you must define what the behavior is in your implementation, and there are a few permissible options”. This is wrong. That is the meaning of what C89 calls “implementation-defined” behavior. The implementation gets a choice; the standard may lay out what the choices are, and in any event the choice must be documented. But with C89's “undefined” behavior, all bets are off. Numerous examples are documented of cases where real compilers were forced to behave in utterly bizarre ways because of real hardware limitations.
- eru 7y agoThe C standard is quite liberal in its use of undefined behaviour. Some cases are due to conflicting existing implementation that predated the standard. (Though implementation defined might make more sense?) Some are for performance reasons. And some are utterly bizarre, like some things that should be syntax errors instead being declared undefined. If memory serves right, that includes eg not closing your string literals. (Thankfully, all implementations I know give you an error message.)
- mjd 7y agoI have two responses to that. The first is that I think your specific example is mistaken. The C89 standard mandates a diagnostic message “for every translation unit that contains a violation of any syntax rule or constraint” (2.1.1.3). It also specifies that a string literal is a sequence of “any member of the source character set except the double-quote ", backslash \, or new-line character” (3.1.4). So my reading is that an attempt to include a newline in a string literal is a violation of a syntax rule that requires a diagnostic, and is therefore not undefined behavior. But I am far from an expert, and I would be glad if you could correct me if I have misunderstood. But my second point is, the people who wrote the C89 standard were really smart. They were compiler writers from many areas of research and industry and worked very hard to find a workable compromise between all of their conflicting needs. It has often happened that I've seen someone say something like “an unclosed string shouldn't be undefined behavior, it should be a syntax error”, and then someone from the committee would show up and say “we wanted to make it a syntax error, but we were supposed to codify existing practice. Widely-Used Compiler X didn't diagnose that, and in fact couldn't, because of …”. And the reason would be something I couldn't have thought of. So I have learned to give them the benefit of the doubt, because they know way more about it than I ever have or will.
- eru 7y agoThanks for the learned response! I just did some research, and https://stackoverflow.com/a/367662/590534 https://stackoverflow.com/a/367662/590534 has a good overview over undefined behaviour on the syntax level. Oh, I don't doubt that the standard authors were smart, and worked within the constraints that were prevalent at the time. Their justified genesis doesn't make the outcomes less bizarre. (And especially for C++, most of the later features are essentially workaround for bad ideas they had earlier.) For the running example about unclosed string literals, I wonder why they didn't make it at least implementation defined behaviour instead? The standards also seem a bit split in their purpose: on the one hand, they often try to codify existing behaviour. On the other hand, they often introduce new features that take compilers years to implement. (That probably applies more to C++ than C.) With the benefit of hindsight, lots of problems could have been avoided if C had come with a module system a bit more sophisticated than automated copy-and-paste of include files via the preprocessor. GCC has lots of flags to give you slightly different dialects of C. The Linux kernel for example tells GCC to never elide null pointer checks and to let signed integers overflow. Haskell also has different dialects, but in addition to compiler flags, you can also specify which variant of the language you are using via pragmas at the top of your file. In most cases, you can mix and match modules written in different dialects; because they get translated to a more stable intermediate representation before they are combined. C doesn't have that luxury with its include files. For C++, there's a long running proposal to add modules to the language. But from all I've read, thanks to all the other complicated features in the language, modules are unlikely to work well for C++. (I'm mostly basing that on https://vector-of-bool.github.io/2019/01/27/modules-doa.html https://vector-of-bool.github.io/2019/01/27/modules-doa.html ) Enough ranting. Summary: I agree that the standard authors make the best effort given the situation. Doesn't make the languages more sensible, though.
- paulddraper 7y agoYep. The author's mis-statement: > In C89, undefined behavior is interpreted as, “The C standard doesn’t have requirements for the behavior, so you must define what the behavior is in your implementation, and there are a few permissible options”. Wrong. The entire point of "undefined" was to mean "the standard imposes literally no requirement." "Implementation-defined" and "unspecified" are essentially what the author is thinking "undefined" means. To quote the standard: > 3.4.1 implementation-defined behavior unspecified behavior where each implementation documents how the choice is made > EXAMPLE: propagation of the high-order bit when a signed integer is shifted right. > 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 > EXAMPLE: the behavior on integer overflow. > 3.4.4 unspecified behavior use of an unspecified value, or other behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance > EXAMPLE: the order in which the arguments to a function are evaluated.
- deleted 7y ago[deleted]
- quelsolaar 7y ago"Ignoring the situation" is not the same as do whatever you want.
- aw1621107 7y agoAnd "do whatever you want" isn't really what compilers are doing either. Out of all possible program executions, they are ignoring the situations where undefined behavior is invoked. That (arguably) matches what the standard says, does it not?
- quelsolaar 7y agoTake the example with writing to NULL. Ignoring the situation means writing to NULL anyway or not writing to the address, not removing the if statement. Making assumptions about the value of a pointer based on UB is not ignoring the situation, that's trying to take advantage of the situation.
- aw1621107 7y ago> Making assumptions about the value of a pointer based on UB is not ignoring the situation But it is? Out of all situations, you ignore those that invoke UB. It's technically correct, but can produce bad results. What you want is to ignore the undefined behavior itself, and to treat the code as conforming. That's an equally valid interpretation, and the only way to definitively get one interpretation over the other is for the standard to change that wording to say so.
- acqq 7y ago> The only way to definitively get one interpretation over the other is for the standard to change that wording to say so. No. If the interpretation of the standard results in a behavior that makes the result obviously worse, nobody should favor such an interpretation. Even if the behavior is "undefined" as per standard, that doesn't mean that any compiler should "define" its own interpretation and implementation in a way to make every resulting program worse. They obviously "can" and obviously are already doing this, but even that doesn't make such an approach good or desired by any user of that compiler.
- TwoBit 7y agoYeah, though that's largely irrelevant to the point of this document.
- tracker1 7y agoLOL... just crash the app, and then document that it's supposed to make demons fly out of your nose and that it only works with blood sacrifice hardware installed. "Demon summoning failed, nasal hardware not found."
- saagarjha 7y agoYou are looking for a version of C that traps on undefined behavior–which is totally fine!–but do know that this would either be something that does not optimize code and/or contains runtime security checks for things that C traditionally does not have checks for.
- lmm 7y agoTrapping implementations have a long and honourable history on platforms where the hardware supports it. Ironically, the whole reason most undefined behaviour is undefined is to permit trapping implementations - implementations that do something safer than just continuing with an implementation-defined value - but instead modern compilers treat it as a license to do something even more dangerous.
- saagarjha 7y agoThankfully, modern compilers have begun inserting traps much more often upon running into undefined behavior.
- gpderetta 7y agoyes of course, you would need a well grounded summoning grid.
- fulafel 7y agoAs a metapoint, the fact that there is this confusing epistemological minefield around the specified semantics is a bad thing in itself, separate from the evils of UB.
- jacinabox 7y agoThe implementation of stdlib necessarily contains code which invokes implementation defined behavior, and the reason that the compiler implementation doesn't treat it in like manner is simply that it doesn't identify it as such or is prevented from doing so. So the treatment of libraries doesn't need to be consistent with that of user programs. Practices that ensure that such code is not optimized away include scattering the information needed to do so across multiple translation units, so that the compiler does not put them together.
- paulddraper 7y agoYou're not wrong but what are the alternatives? (1) Have a vague spec that doesn't distinguish between defined and undefined behavior. (2) Define everything, at the cost of performance/hardware support. Remember that C works on 50-year-old hardware, and current hardware.
- fulafel 7y ago(2) is the usual standard in computing. It's not like there aren't any performance or hw support compromises in current C.
- MaulingMonkey 7y agoThe author claims that, recently, the ocean has been getting concerningly wet. GCC 1.17 dates back to 1988 and would launch nethack as an easter egg in response to something as tame as implementation defined behavior (#pragma use.) This is incorporated into a gamedev variation on "nasal demons" I've heard/parroted: "It can launch Nethack, it can launch nuclear missiles at a cow ranch in Alaska, or worse - it can do exactly what you expected it to do." The first two being perfectly standards-compliant behaviors when your code has an RCE vulnerability thanks to invoking undefined behavior via, say, buffer overflow. The last turning compiler upgrades into a minefield, and bug trackers into a dumping ground for once-encountered heisenbugs.
- kps 7y agoI've become convinced that the original ANSI committee didn't intend to create the monster they did. The reason is that Dennis Ritchie wrote a submission to the committee¹ that 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”, and, that “[i]t negates every brave promise X3J11 ever made about codifying existing practices, preserving the existing body of code, and keeping (dare I say it?) ‘the spirit of C.’” Those comments describe what ‘undefined behaviour’ turned in to. The only reason dmr and others didn't make the same objections to it is that nobody realized at the time what the committee wording implied. ¹ available here: https://www.lysator.liu.se/c/dmr-on-noalias.html https://www.lysator.liu.se/c/dmr-on-noalias.html
- amluto 7y agoI don't buy these arguments. Suppose you have a little bit of UB: char foo[10]; foo[10] = x; That's a buffer overflow, and a clever attacker can supply input for which this really will format your hard drive. Writing a standard that allows literally anything for this particular example of UB but somehow requires sensible behavior for other types of UB is not obviously possible. Merely saying that it can't happen doesn't make it so.
- kps 7y agoI believe the committee's intent was, as they stated, to ‘codify existing practice’. That means that the compiler generates code for the statement as written (in this case, a store into location foo+10) and what happens when it runs is up to the machine and not the compiler's problem. At the time ANSI C was under way, I was working on a commercial C compiler and following developments closely. I don't think anyone anticipated what ‘undefined behavior’ would turn into.
- Rusky 7y agoThe problem with that approach is it inhibits a lot of perfectly-sensible optimizations. If you force the compiler to leave it up to the machine, you force it to preserve the same stack layout, the same memory write, and the same execution order relative to everything else. You basically forbid it from working with an execution model at any level higher than "a giant array of bytes," because now you have to preserve the naive behavior of that giant array of bytes. But this is not really useful for correct programs. The committee's intent may have (accidentally, indirectly) been to codify compilers that do very little optimization, but that doesn't mean it was a good idea.
- 3JPLW 7y agoRegardless of the cause (if it's that "one word" or not), the behaviors are crazy-making and there's a trend of more aggressively exploiting UB for optimizations.
- kazinator 7y agoThe author's interpretation is completely spot on. C89 does not allow the implementation to deliberately break a program that invokes undefined behavior, unless they document what they are doing. It may allow the program to break due to ignoring the situation. > There is absolutely no requirement to document what those "unpredictable results" might be. Only in that case: when the situation has been ignored completely. If the situation has not been ignored completely (for instance, the compiler analyzes for the absence of that type of undefined behavior and optimized based on that), then it is not permissible to behave with unpredictable results. The behavior now falls into a "documented manner characteristic of the implementation", or else terminating at translation or execution time with or without the issuance of a diagnostic. One way to look at this is that they changed "permissible" to "possible" for a reason. What might that reason be? Almost certainly that the word "permissible" gives requirements.
- aw1621107 7y ago> If the situation has not been ignored completely (for instance, the compiler analyzes for the absence of that type of undefined behavior and optimized based on that) Except optimizing based on undefined behavior is also a form of ignoring the situation. For example, say you have code that can: 1. Execute Action A if undefined behavior is present, or 2. Execute Action B if undefined behavior is not present. A compiler could look at these two possibilities, see that one of them invokes undefined behavior, ignore that situation completely, and optimize the remaining path. And that would be perfectly in line with the wording of the standard. The interpretation you and other commentors who dislike optimizations based on UB favor is that "ignoring the situation" should mean "treat the code as if it was conforming". Both are valid readings of the standard, and the only way one will become the dominant reading would be for the standard to use less ambiguous wording.
- kazinator 7y ago> Except optimizing based on undefined behavior is also a form of ignoring the situation. That is illogical. 1. The optimizing is based on undefined behavior (your exact words). (Actually it is based on the assumption of freedom from a specific undefined behavior, but the assumption is somehow tied to that case of undefined behavior.) 2. The situation that is to be "ignored completely" is the undefined behavior. 3. So the "based on" and "ignore completely" both refer to the undefined behavior. 4. Contradiction: "based on" is quite opposite to "ignoring completely". You can only be doing one or the other. Also, you're neglecting to provide a plausible hypothesis as to why "permissible" had to be changed to "possible", if it makes no difference in meaning. I don't think that the original ANSI C committee would have signed off on that change. It's not an interpretation of existing committee intent, but a case of new people changing the language. The purpose of, say, numeric overflow being undefined is that implementations didn't agree on how it was handled. Compilers simply passed that on to the machine code. that's what it means to "ignore the situation completely": just compile the code, and let whatever happen in the translation. Programmers who understand the machine could infer the behavior anyway, for their platform. > see that one of them invokes undefined behavior, ignore that situation completely Removing something from consideration after evaluating it is not compatible with the wording "ignore completely". It might be compatible with "ignore"; I could swallow that much. Definitely with the "completely" though. The C89 authors added that adverb there for a reason. > And that would be perfectly in line with the wording of the standard. Well, with the current wording, not the historic wording in C89.
- radisb 7y agoHonest question, since I have no expertise in the field: If C89 says that "Permissible undefined behavior" includes "ignoring the situation completely with unpredictable results" can you give an example of something that would constitute non permissible undefined behaviour according to the standard?