6 ms·
The reason for having UB in the first place is so compilers can exploit them so we can have more optimized programs. UB is a feature, not a bug. So you don't r
by shultays 6y ago
The reason for having UB in the first place is so compilers can exploit them so we can have more optimized programs.
UB is a feature, not a bug. So you don't really "clean" it up. Cleaning up of UB means a lot of overhead for programs.
- geofft 6y agoYes, but also, UB is a "feature" to work around the fact that C is a relatively un-expressive language with a simple type system. It's great that the compiler can optimize for (i = 0; i < *len; i++) array[i] = 0; without having to check on each iteration whether len secretly points inside the array and got modified by the loop. It would be better if this were not a heuristic and there were clear compile-time information saying for sure that this optimization is safe.
- sharpneli 6y agoIf len and array are both of the same type then the compiler must assume they can alias (or one of them is char*). One has to explicitly state with restrict that they cannot alias. In case of similar but where it’s float array[N] etc then compiler can assume that they do not alias.
- geofft 6y agoRight, that's a heuristic, and the consequence of that heuristic is that a bunch of things where you intentionally have pointers of different types to the same memory - the sort of low-level data manipulation that C ought to be good for - is UB unless you are careful about it and you find one of the exceptions to this rule.
- sharpneli 6y agoNot a heuristic. Well defined, even if it could be defined better. I'd personally make all types not alias by default and then have a separate keyword 'alias' to specify things that must alias. One of the reasons why Fortran was faster for decades was the default aliasing rules, restrict finally got us out of that trap. If we'd always assume everything alias plenty of code would become way slower. Also C has well defined ways to access memory via different types. Make an union type and access via that. It makes it explicit.
- AlotOfReading 6y agoIs it really fair to call type punning "well defined"? To my knowledge, it was considered implementation defined from C89 all the way through C11.
- sharpneli 6y agoBefore C11 char* was the only way to do type punning, badly. So it was basically forbidden, but still well defined. Since then they specified that unions are the way to go.
- geofft 6y agoIt's deterministic, but it's still a heuristic in the sense that it's a guess at the underlying intent of the code. You can come up with different, perhaps simpler guesses, but they're still guesses. It's quite common in existing real-world C code to rely on the ability to fill in a variable via a pointer of one type and then access it via a pointer of another type, and you expect the language to properly return the data you wrote to that pointer. The C committee determined that, just about always, that happens when one of the pointers is a char pointer (e.g., you call read() or write() on a struct), and they said that char pointers are allowed to alias by default but others can't. That's their guess, which is mostly accurate. There are much nicer answers here if you're not obligated to be compatible with existing C code - but in that case, especially today, I'd go with one of several other languages that can accurately express intention at the source code level instead of making backwards-incompatible changes to C. :)
- jzwinck 6y ago"restrict" does exactly what you're looking for.
- rurban 6y agoIf it wouldn't be broken it would indeed be useful. With gcc restrict is still broken, you have define it away. clang had fixed it just recently I think. https://github.com/autoconf-archive/autoconf-archive/pull/209 https://github.com/autoconf-archive/autoconf-archive/pull/20...
- bitwize 6y agoThe reason was originally to admit portability to different architectures and different environments. The primary difference between undefined behavior and implementation-defined behavior is that implementation-defined behavior must be accepted by the compiler and yield a working program. Undefined behavior can error out at compile or run time -- but it doesn't have to. Some examples: 1. When C came out, you couldn't assume a two's complement machine. So some architectures would yield different results on signed over/underflow than others, and some architectures might trap on overflow. Hence, signed overflow is undefined in C. 2. Did you know the Lisp Machine had a C compiler? It did. Because pointers on the Lisp machine were tagged and bounds-checked to ensure memory safety, this compiler implemented C pointers with fat pointers, containing a reference to an object and an offset within that object. Most machines you are familiar with allow indirecting through arbitrary integers; the Lisp Machine is an example of one that does not, trapping on invalid pointer accesses. Having C and C programs available in both environments mean that indirecting through a pointer that does not point to or inside some pre-existing static or dynamically allocated object is undefined. 3. Operating systems with virtual memory can arrange things such that indirecting through 0 (NULL) will raise a segmentation violation in user code, guaranteeing a pointer value that is always invalid -- but inside the kernel you may want to access memory location 0. So accessing null pointers is undefined. By leaving these undefined, you are not committing implementors to adapt the abstract machine to the underlying hardware in cases where hardware can widely differ, allowing for simple implementations that yield fast code on all architectures they compile for yet still allowing for a useful subset of C code to be 100% portable.
- shultays 6y agoYou can force portability and remove UB at the same time. For example the system is not two's complement? just wrap everything math operation with checks on those system and say that your language is two's compliment! Or just does what C does, make it UB and the programmers will be aware of implementation of overflows and won't suffer because of added checks. They will do the checks themselves if they want and handle their numbers with care Imo the answer always boils down to performance. Java has no UB yet it works on billion devices (citation needed). It is doable, just costly.
- fanf2 6y agoThe rationale does not mention optimization as a reason for undefined behaviour https://www.lysator.liu.se/c/rat/a.html#1-6 https://www.lysator.liu.se/c/rat/a.html#1-6
- shultays 6y agoEh, well but it is. Since most (all?) UB could be eliminated if you added more code. I don't see any other reason for stuff like making access to uninitialized memory UB for example.
- deleted 6y ago[deleted]
- flohofwoe 6y agoWhat I mean with "pragmatic cleanup" is getting rid of obsolete assumptions about the underlying hardware. E.g. two's complement has won, there's no reason why this couldn't be reflected in the C standard by removing signed overflow from the list of undefined behaviour. I bet more such cases can be found (e.g. 8-bit bytes, NULL not being zero, etc...). Even if such exotic hardware is still around, the only thing that would change is that C compilers supporting this hardware couldn't call themselves "ISO Compliant" anymore, the users of those compilers wouldn't lose anything.
- shultays 6y agoC++20 is actually forcing two's complement I think. So it is happening slowly