5 ms·
C needs to cut down on the instances of undefined behavior. Mostly these should become implementation-defined with some restrictions that the implementation sh
by dzdt 11y ago
C needs to cut down on the instances of undefined behavior. Mostly these should become implementation-defined with some restrictions that the implementation should do one of the expected things.
For instance, if you overflow an integer, the compiler should not be allowed to assume that code branch is unreachable, or to conjure nasal demons. Instead the result should be an implementation-defined integer or a jump to an implementation-defined exception handling routine.
C should be predictable!
- pcwalton 11y ago> C needs to cut down on the instances of undefined behavior As someone who's worked on compilers a good bit, I disagree (and this is my favorite meme to argue against lately). You need that undefined behavior for performance. > For instance, if you overflow an integer, the compiler should not be allowed to assume that code branch is unreachable, or to conjure nasal demons. You need to be careful to not destroy loop trip count detection. > Instead the result should be an implementation-defined integer Not good enough. Loop trip count detection destroyed. > jump to an implementation-defined exception handling routine. Unacceptable for performance. Probably a 3x loss.
- microcolonel 11y agoI think that the reason people want this stuff is that they don't understand how much effort it takes to prove that underlying UB is not triggered in a branch and optimize out checks. Code size would explode, too; decimating the usefulness of embedded platforms, and eviscerating instruction caches. In that way, it would also be an ecological disaster.
- regehr 11y agoMeanwhile, Java runs pretty fast.
- pcwalton 11y agoJava doesn't suffer from these a bunch of these limitations, though, especially around aliasing. That makes a big difference. (Though signed indexing is still an issue in Java, unfortunately…)
- haberman 11y agoI'm curious to learn more about your position -- have you written it down in more detail elsewhere? Rust doesn't have this kind of undefined behavior AFAIK? Are you arguing that C and C++ should have it, but Rust doesn't need to even though it's competing perf-wise with C and C++ in this space?
- microcolonel 11y agohttps://doc.rust-lang.org/reference.html#behavior-not-considered-unsafe https://doc.rust-lang.org/reference.html#behavior-not-consid... >• Integer overflow > ◦ Overflow is considered "unexpected" behavior and is always user-error, unless the wrapping primitives are used. In non-optimized builds, the compiler will insert debug checks that panic on overflow, but in optimized builds overflow instead results in wrapped values. See RFC 560 for the rationale and more details. (also important to note that when they say "RFC 560", they are referring to Rust RFCs and not IETF RFCs)
- pcwalton 11y ago> Are you arguing that C and C++ should have it, but Rust doesn't need to even though it's competing perf-wise with C and C++ in this space? Yes. Other languages like Swift generally don't need it either. The big wins from exploiting undefined behavior in C come from mistakes that C's designers made in 1978: signed int being the easiest thing to use for loops over array indices, the any-pointer-can-alias-anything-else style, the uselessness of const for optimization, and null pointers. Rust (and e.g. Swift, for the most part) made none of these choices, and so they can reap the benefits of aggressive compiler optimizations without the undefined behavior. (Digression: I can't blame C's designers for these mistakes, of course: it was 1978! But I think that in 2016 we should just accept that there were things we didn't do right in 1978, because we didn't know as much then as we do now. In most other engineering industries the idea that we made mistakes 35 years ago and that modern designs are better is uncontroversial, but for some reason in computing we have rose-colored glasses for the early days of Unix. Everyone wants to blame the compiler authors because they don't want to admit C has flaws!) These issues don't necessarily apply to other languages. I believe that if you design a language optimally you don't need to fall back on undefined behavior to get good performance. But I don't believe C is that language, and I think attempts to dial back undefined behavior without changing C are missing the point.
- samuellb 11y agoI actually prefer undefined behavior to defined but not necessarily desirable behavior, such as values defaulting to zero/NULL or integer rollover. That's IMHO only hiding the symptoms (e.g. crashes) of the problems (e.g. logic bugs). Undefined behavior allows the compiler to handle those cases as errors (e.g. print stack trace and crash). Or to perform static analysis. Or, to do something predictable.
- ori_b 11y ago> Undefined behavior allows the compiler to handle those cases as errors (e.g. print stack trace and crash). Or to perform static analysis. Or, to do something predictable. The term you're looking for here is "implementation defined".
- _kst_ 11y agoNot really. "Undefined behavior" is behavior that is not defined by the language standard. A given implementation is free to define the behavior as it likes.
- msbarnett 11y ago> Undefined behavior allows the compiler to handle those cases as errors (e.g. print stack trace and crash). Or to perform static analysis. Or, to do something predictable. In practice, no existing compiler that I'm aware of issues a stack trace on undefined behavior at runtime. Instead, the compiler reasons from the perspective that if a section of code would have undefined behaviour under some set of circumstances, it will assume that those circumstances will never occur, and optimize accordingly (with sometimes surprising results).
- uxcn 11y agoPointer arithmetic overflow frightens and confuses me. Does anyone know why it's treated differently than unsigned arithmetic?
- gdwatson 11y agoI assume it's because the standard doesn't guarantee a linear address space.
- david-given 11y agoThe standard doesn't even guarantee that pointer arithmetic works at all, outside the bounds of an object (or at least did in C89, which is the only standard I know well). If you have an object which is ten bytes long, like: uint8_t o[10]; ...then it's legal to construct pointers to o[0] through o[10] --- not o[9]; you can create a pointer to the byte immediately after the object --- and nowhere else. Like, it's not even legal to calculate one, let alone dereference it. I used this to make a prototype compiler from C to Javascript/Perl/Lua, where each C pointer was represented as a tuple of (array, offset). Pointer arithmetic worked inside objects; pointer arithmetic between objects wasn't supported. Worked nicely.
- uxcn 11y agoAccessing beyond bounds is undefined, but leaving pointer arithmetic outside of objects undefined would preclude a lot. For example, building an OS page table or DMA, or RDMA, or MMIO, etc...
- pcwalton 11y agoNo, even out-of-bounds arithmetic is undefined. 6.5.6 Additive operators paragraph 8 (cribbed from Stack Overflow [1]): When an expression that has integer type is added to or subtracted from a pointer, the result has the type of the pointer operand. [...] If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined. [1]: http://stackoverflow.com/questions/18186987/decrementing-a-pointer-out-of-bounds-incrementing-it-into-bounds http://stackoverflow.com/questions/18186987/decrementing-a-p...
- gdwatson 11y agoI think this is the right spirit. C makes a lot of things undefined that could be implementation defined even on weird hardware. That would considerably reduce the gap between customary C and standard C for "normal" hardware while letting the oddball machines do what they need to do and document it. I don't know if the compiler implementors would stand for it, though.
- pcwalton 11y ago> I don't know if the compiler implementors would stand for it, though. I wouldn't without solid proof that performance won't regress.
- gdwatson 11y agoHow much? On which applications?
- pcwalton 11y agoThe applications that the compilers' customers are using. The thing that's often forgotten in these discussions is that compiler authors listen to their customers, and what they do reflects what their customers want. Compiler developers don't implement optimizations because they're scheming language lawyers looking for ways to break programs. They implement optimizations because their customers file bugs saying that a competing compiler optimized their program a certain way and wondering why all compilers don't do the same. It's instructive to go back and look at mailing list and message board discussions at the height of the GCC/LLVM/MSVC/ICC competition. The performance wars were intense.
- pklausler 11y agoAgreed. Integer overflow, in particular, should be implementation-defined, and implementations should have a means to indicate whether they wrap around in two's- or ones'-complement, clamp, signal, or do something else.
- xamuel 11y agoC does have an all-behavior-defined mode. It's called valgrind. Try running a serious production program in it and then tell us about how trivial those UB-based optimizations are.
- ArkyBeagle 11y agoThis too...
- jzwinck 11y agoValgrind is great. But its slowness does not arise from disabling UB optimizations.
- ArkyBeagle 11y agoIt's too much furniture to do that properly. You may want to overflow an integer. Granted, rings of cardinality 2^64 are unusual at best but I've used 8 & 16 bit size rings forever. UB is just the nature of the beast.
- catwell 11y agoHe was talking about signed integer overflow. Unsigned integer overflow is useful and defined.
- ArkyBeagle 11y agoHe sort of didn't say, so you're probably right. I know that's a PITA. Good thing there are 64 and 128 bit ints.