6 ms·
The only reasonable thing to say about this was already said upthread of the page, and quoted here: > I have worked on many programs, and whenever I found such
by copsarebastards 11y ago
The only reasonable thing to say about this was already said upthread of the page, and quoted here:
> I have worked on many programs, and whenever I found such a problem (typically called a "portability problem"), where the code was assuming something that the language did not guarantee, I fixed the program rather than bitching about the compiler.
- geofft 11y agoIMO, it's a little subtler than that. It's not the compiler's fault, it's the language's. Plenty of languages have no undefined behavior that can be written by accident. Go and safe Rust, for instance, have just about no undefined behavior at all, and are both performance-competitive with C. (Go has UB if you cause race conditions, and Rust has UB within `unsafe` blocks analogous to C's UB.) A C compiler, meanwhile, has to aggressively take advantage of undefined behavior to get good performance, and the C specification has been keeping behavior undefined for the benefit of compilers. You can hope that you find all such problems in C (which you might not) and "fix the program", but you can also "fix the program" by switching to a better language.
- GPGPU 11y agoYou can't compare traditional GC languages like "Go" to C. They inhabit two different universes.
- geofft 11y agoLeaving aside the fact that I'm also comparing Rust... why not? It's a language that produces fast, static executables. I bet that a good fraction of the Debian archive (not all of it, for sure) could be reimplemented in Go without causing any problems. What "different universes" are these? (To be fair, I haven't written any Go because my personal use cases involve things like shared libraries and C-ABI compatibility, so I'm going off what I've heard about Go, not personal experience. But out of what I've heard about Go, it's a fine language for this purpose, because the requirement here is just portability to all Debian architectures and comparable performance, and whether GC is used is an implementation detail.)
- RyanZAG 11y agoUmm wait. Undefined behavior is where the language specification is not 100% precise, and compiler implementations can differ on produced code. Go and Rust only have single implementations. The specification for both are very brief. Are you claiming that a clean box implementation of Go and Rust would always behave identically? I have only one thing to say: I clicked through the Golang spec for 30 seconds and found this: https://golang.org/ref/spec#Run_time_panics https://golang.org/ref/spec#Run_time_panics > The exact error values that represent distinct run-time error conditions are unspecified. Oh, what's that? Undefined behavior? In the golang spec?!
- barrkel 11y agoYour definition of undefined behaviour is actually the definition for unspecified behaviour. Unspecified behaviour is usually intentional ambiguity either to give wiggle room for an optimizer, or to accommodate platform variance. Writing a program with that invokes unspecified behaviour isn't normally a problem, as long as you're not relying on a specific result. Order of argument evaluation is a common example. Relying on undefined behaviour is almost always bad, and almost always avoidable. That's where the nasal demons come from. Dereferencing null, alias-creating pointer typecasting, etc.
- im3w1l 11y agoUndefined behavior has a very specific meaning for C. It doesn't mean "not 100% precise". It means 100% imprecise. The C standard only gives any guarantees on what your program will do if you never invoke UB. If you do, well then it can do literally whatever it wants including deleting all your files.
- MaulingMonkey 11y agoLiterally deleting all your files at that. It's the part of the language that says your compiler is completely justified in allowing you to write that buffer overflow vulnerability that can trample executable memory, which is then used by a malicious attacker to do literally whatever they want, including deleting all your files. Or worse.
- 11y ago
- copsarebastards 11y agoI don't think it's the language's fault either: C is four decades old, and being bound by reverse-compatibility, they can't integrate much of the programming language research that has happened in the last four decades. I'm a less concerned with placing fault for the problem than I am with placing the responsibility for fixing the problem, and that's clearly on the writer of the program which uses undefined behavior. I think choosing Rust might be a reasonable way to avoid the problem in the first place, so I agree with you there, but there are also reasons to choose C over Rust. Personally, I write a lot of C code because I prefer to build on a GPL stack. This is one of the reasons I'd like to see Rust added as a GCC language. Sadly I don't have the time to do it myself.
- EdiX 11y agoYes, the C language specification is a bit shit, it leaves too much leeway to compilers so that C compiler for broken, niche architectures can be written. However, I disagree with you. All this badness in the C specification did not stop people from writing reasonable C compilers for reasonable architectures for decades. The real problem here is competition. Gcc is in a competition with clang to produce fast code which makes the gcc developers feel justified when they exploit undefined behaviours for marginal optimizations. This is a case of following the letter of the law (in this case the C standard) while disregarding its spirit: all the undefined behaviour was so that C compilers could accomodate for odd architectures while remaining close to the metal, not so that compiler programmers could go out of their way to turn their compiler into a mine field.
- makomk 11y agoMore specifically, they're competing to produce the fastest code for software that follows the C specification to the letter. That's not necessarily the same as producing the fastest code that actually achieves the intended goal. For example, if I recall correctly the popular Opus codec overflows signed integers when decoding invalid data, and so long as this can be guaranteed to produce some (possibly implementation-specific) result this is perfectly safe. However, this is technically undefined behaviour - a particularly malevolent optimising C compiler could decide to give the sender of the data arbitrary code execution, because it's allowed to do whatever it likes. This might even make the code run faster, but it'd make decoding Opus correctly and safely slower because the decoder would have to do a bunch of gratuitous overflow checks on operations it could otherwise just let overflow. Fortunately, gcc hasn't reached that level of advanced malevolence yet.
- copsarebastards 11y ago> More specifically, they're competing to produce the fastest code for software that follows the C specification to the letter. That's not necessarily the same as producing the fastest code that actually achieves the intended goal. That's true, but "producing the fasted code that actually achieves the intended goal" is not a problem which can be solved by a compiler--the compiler can't read your mind. Attempting to represent a reasonable approximation of reading your mind is the responsibility of the specification. You can definitely do better than C, but I doubt you could have done better 4 decades ago when C was designed.
- ridiculous_fish 11y agoThis makes it sound like Go and Rust have some secret sauce that enables C's performance without UB. But it is not so. For example, consider an expression like (x*2)/2. clang and gcc will both optimize this to just x, but Go and Rust will actually perform the multiplication and division, as required by their overflow semantics. So the performance vs safety tradeoff is real.
- kibwen 11y agoI can't speak for Go, but your intuition regarding Rust code is incorrect. See https://play.rust-lang.org/?gist=09b464627f856e0ebdcd&version=nightly https://play.rust-lang.org/?gist=09b464627f856e0ebdcd&versio... , click the "Release" button, then click the "LLVM IR" button to view the generated code for yourself. TL;DR: the Rust code `let x = 7; let y = (x*2)/2; return y;` gets compiled down to `ret i32 7` in LLVM. In fact, there is "secret sauce" here. The secret sauce is that Rust treats integer overflow specially: in debug mode (the default compilation mode), integer overflow is checked and will result in a panic. In release mode, integer overflow is unchecked. It's not "undefined behavior" in the C sense, because of the fact that it always returns a value--there are no nasal demons possible here (Rust disallows UB in non-`unsafe` code entirely, because memory unsafety is a subset of nasal demons). The exact value that it returns is unspecified, and the language provides no guarantee of backward compatibility if you rely on it (it also provides explicit wrapping arithmetic types if you explicitly desire wrapping behavior). And though it's a potential correctness hazard if you don't do any testing in debug mode, it's not a memory safety hazard, even in the unchecked release mode, because Rust's other safety mechanisms prevent you from using an integer like this to cause memory errors.
- dbaupp 11y agoA constant expression is not a good test: any compiler worth its salt (which includes LLVM) will be able to optimise a case like that. Change the function to: pub fn test(x: i32) -> i32 { let y = (x*2)/2; y } and you'll see the difference (e.g. compare against a similar function on https://gcc.godbolt.org/ https://gcc.godbolt.org/ ). > The secret sauce is that Rust treats integer overflow specially Debug vs. release mode is irrelevant: the panicking case is more expensive than any arithmetic, and, the RFC[0] was changed before it landed: > The operations +, -, [multiplication], can underflow and overflow. When checking is enabled this will panic. When checking is disabled this will two's complement wrap. The compiler still assumes that signed overflow may happen in release mode, and that the result needs to be computed as per two's complement, i.e. not unspecified. [0]: https://github.com/rust-lang/rfcs/blob/master/text/0560-integer-overflow.md https://github.com/rust-lang/rfcs/blob/master/text/0560-inte...