16 ms·
One thing that is not mentioned in the article, is that next to undefined behavior, there is also implementation defined behavior. For example, if signed integ
by phicoh 5y ago
One thing that is not mentioned in the article, is that next to undefined behavior, there is also implementation defined behavior.
For example, if signed integer overflow would be implementation defined behavior, then any weirdness would be limited to just the integer operation that overflows.
Lots of other stuff can be expressed as implementation defined behavior. That would probably kill some optimizations.
So the question is more, do we want a portable assembler? In that case as many C constructs as possible need have defined behavior. Either defined by the standard or as part of the compiler documentation.
Another possibily is to have standards for C on x86, amd64, arm, etc. Then we can strictly define signed integer overflow, etc. And say that on x86, pointers don't have alignment, so a pointer that points to storage of suitable size can be used to stored an object of different type, etc.
If the goal is to run SPEC as fast as possible, then making sure every program trigger undefined behavior is the way to go.
- ErikCorry 5y agoIn theory the difference between undefined behaviour and implementation defined behaviour is that ID behaviour must be documented. In practice good luck finding that documentation for each CPU and compiler combination. In fact good luck just finding it for LLVM and x64.
- josephcsible 5y agoNo, that's the difference between unspecified and implementation defined behavior.
- ErikCorry 5y agoIf you want to be pedantic about it then I guess Clang and GCC don't implement the standards since they treat implementation defined as unspecified.
- astrobe_ 5y agoThis unspecifed behavior is perhaps a bit lesser-known than UB, here is a (hopefully non-null ;-) pointer: https://stackoverflow.com/questions/18420753/unspecified-undefined-and-implementation-defined-behavior-wiki-for-c https://stackoverflow.com/questions/18420753/unspecified-und...
- palotasb 5y agoUndefined behavior entails "there are no restrictions on the behavior of the program", meaning anything can happen, including executing the opposite of the program text. Implementation defined behavior is saner in the sense that program behavior is still defined. Examples of the latter are the exact type of std::size_t or the number of bits in a byte: https://en.cppreference.com/w/cpp/language/ub https://en.cppreference.com/w/cpp/language/ub
- immibis 5y agoThe linked reference page does not say that implementation-defined behaviour must be sensible, only that it must be defined. Contrast with unspecified behaviour where "Each unspecified behavior results in one of a set of valid results." I expect that most instances of implementation-defined behaviour come with additional rules which state that the implementation has to define something sensible.
- wruza 5y agoI have a dumb question. Why can “we” write pretty good apps in languages other than C, but can’t write operating systems? Is talking to hardware so much different than talking to APIs? Another point of view on the same question: Looking at software and hardware, the latter evolved insanely, but the former didn’t get seemingly faster, at least in userlands. Why bother with UB-related optimizations at all for a wide spectrum of software? Is there even software which benefits from -O3 and doesn’t use vectorization intrinsics? Why can’t “we” just hardcode jpeg, etc for few platforms? Is that really easier to maintain opposed to maintaining never ending sources of UB? Iow, why e.g. my serial port or ata or network driver has to be implemented in C, if data mostly ends up in stream.on(‘data’, callback) anyway?
- eatbitseveryday 5y ago> Why can “we” write pretty good apps in languages other than C, but can’t write operating systems? Is talking to hardware so much different than talking to APIs? Operating systems are written in other languages, such as C++ and Rust. One requirement is that a language must be compiled and thus cannot rely on a runtime. That excludes Go and Java. The language needs to support direct memory manipulation. The compiled binary cannot be emitting system calls since that binary IS the kernel. Thus the compiler must be told to not link or include standard libraries. You need to disable certain optimizations like advanced vectorization extensions and red zone use on the stack. There are others. Lots of specific control needed.
- ArbixMenix 5y ago>One requirement is that a language must be compiled and thus cannot rely on a runtime. That excludes Go and Java. Maybe I'm wrong, but I know that there exist CPUs made specifically to natively execute Java bytecode, so in reality if the hardware has a baked-in language interpretation it would be actually possible to write an OS completely in Java
- bluGill 5y agoTrue, you can design a CPU for anything. However a OS that depends on such a CPU is not portable to anything else, and can't easily run most programs that people depend on (emulators are possible, but not easy). Also most CPU advances haven't gone into such a thing and it is tricky to apply those advances while also providing what the language needs. None of this is impossible, but it makes such CPUs in todays world of questionable value. Note that you can port any OS written in C to such a CPU with "just" a new compiler backend and a few drivers. Your OS won't take advantage of the features the CPU provides, but it will work.
- bluecalm 5y agoI don't think making it defined would help much. Overflowing a signed integer is a bug in logic. It would be ideal to have a crash on that. Continuing is going to be bad one way or another unless you luck out with your buggy code so the way the implementation works saves you. It can't be relied upon in general case though. Imo the way is to develop more tools that detect (either by analysis or at runtime) those bugs and run the code with those attached as often as you can afford it (to take the performance penalty).
- phicoh 5y agoThat's the thing. When C is portable assembler, you expect signed integer overflow to be the same as in assembler. On x86 you don't expect a trap. There are quite a few idioms where overflow is used intentionally. There is no reason to turn that into undefined behavior if it works fine on the underlying platform.
- bluecalm 5y agoI don't expect C to be portable assembler. I expect it to be a simple fast language with clear rules. I am not sure what you mean by an idiom here. I suppose you mean assembler idiom as in C it was always a simple bug in logic. Obviously you can't just use idioms from one language in another without checking what the rules in the other language are. Platform specific behavior should be as rare as possible. It's a recipe for bugs. The rule is simple enough and major compilers have flags to prevent the overflow. Obviously you pay the performance penalty for using them. It's a choice you're free to make as it should be.
- astrange 5y agoThere isn’t a “the same as assembler” that’d make sense in C. For instance ARM, x86 scalar, and x86 SIMD all have different integer overflow rules for shift operations.
- rini17 5y agoSo you're fine with throwing your hands up "its unreliable, its a bug that should never happen" when standard lacks a clear recipe how to check beforehand whether signed integer arithmetic will overflow? Everyone rolls their own, introducing even more bugs.
- bigcheesegs 5y agoYou don't actually want implementation defined behavior. There is no restriction on implementation defined behavior, it just needs to be documented. Suitable documentation includes "the optimizer assumes this never happens and optimizes accordingly.", or "Look at the source code."