CppCon 2016: Chandler Carruth “Garbage In, Garbage Out: Arguing about Undefined Behavior..."
CppCon4 HN recommenders · 2016–2026
94K views · talk · evergreen · Uploaded 2016-10-06
Chandler Carruth's CppCon 2016 talk tries to defuse the heated debate over undefined behavior in C and C++. He explains from the compiler writer's perspective why UB exists and how it enables optimization.
HN points to it whenever undefined behavior comes up, as the best explanation of the reasoning behind UB, though some dispute its framing.
49:55How Python was Shaped by leaky Internals, Armin Ronacher, Flask framework
Видео с мероприятий {speach!
36:45Rich Harris - Rethinking reactivity
You Gotta Love Frontend
1:12:27CppCon 2015: Andrei Alexandrescu “std::allocator...”
CppCon
51:18Lexical Scanning in Go - Rob Pike
GoogleFOSSSydney
1:31:06William Byrd on "The Most Beautiful Program Ever Written" [PWL NYC]
PapersWeLove
1:02:42Preventing the Collapse of Civilization / Jonathan Blow (Thekla, Inc)
DevGAMM
42:15"Performance Matters" by Emery Berger
Strange Loop Conference
1:48:55The Thirty Million Line Problem
Molly Rocket
14 comments from Hacker News
I very much doubt it, as it goes against the C culture of micro-optimize each code line as it is written, without any feedback from a profiler because "the coder knows best!". Since I know C (1992), I have seen quite a few times people write code to go fast as they can and apply compiler optimization tricks for use cases where it hardly matters. Also many of the issues regarding UB go back to the early ANSI C days, when it was the wild west of C dialects outside UNIX and no vendor wanted to give up on their own extensions. As for C and C++ compiler writers view on UB: [VIDEO]
[VIDEO] Thank you very much. Maybe the defenders of idiotic compiler behaviour can go now and hide under a rock. As for the slide at [VIDEO] Correct or incorrect programs are defined in terms of the language. With an underspecified language like C you get what you deserve: -O2 being a different language than -O0 I've seen enough of this video where incorrect programs are being equaled to undefined language behavior. I'll stop looking further.
The generally recommended thing for catching & fixing this is: [LINK] CppCon has also had a lot of great talks on UB and why it is what it is, such as [VIDEO]
Chandler Carruth's talk on the subject was really useful for my own understanding of the reasoning behind UB: [VIDEO]
Actually I discourage my colleague to use unsigned especially because they can overflow. The problem with overflow is that most of the time it is a program bug if there is an overflow that wasn't intended. Using signed arithmetic means that you can actually trap on overflow and catch these bugs (using fuzzing for instance). Chandler explained it nicely in his CPPCon 2016 talk "Garbage In, Garbage Out: Arguing about Undefined Behavior...". I encourage to watch the full talk but here is one relevant example: [VIDEO] And here he mentions how Google experimented with this internally: [VIDEO]
I haven't actually watched it in a while, but are you referencing this talk by Chandler Carruth? [VIDEO]
Signed integer overflow causing undefined behaviour allows the compiler to make significantly better optimisations. Here is Chandler Carruth, and clang developer, talking about it: [VIDEO]
It's not obvious unless you read and think about the assembly, but bonzini's point was that using a 32 bit signed int (which is not allowed to overflow) means the compiler can cleverly promote it to use register RAX as an index without any other shenanigans to deal with 32 bit overflow. RAX is a 64 bit register for integers or pointers. There are reasons why this might be advantageous over using EAX, which is the name for the low 32 bits of RAX. It's very possible that bonzini is referring to the example in the following link: [VIDEO] Back to your overall point though, you're right that there's frequently an advantage to having your bulk data use smaller data types. It's better for caching and memory bandwidth, and it opens up more possibilities for parallelization with SIMD or GPU magic. However, it's really a false economy for things like loop indices. It doesn't matter if you declare your variable to be int8_t using just 1 byte, because it's probably going to tie up an entire 64 bit register anyways. There are ways to use only parts of registers, so you can pretend you have more registers, but these cause dependency stalls and are generally not good for modern processors, or at least not a win for speed. To a lesser extent, it's a false economy with floating point on the CPU too. Your 32 bit float is likely taking up an entire 128 bit (or larger) SIMD register, and the operations generally have the same latency and throughput as their 64 bit (double) equivalent. The rules
Suggestions like this are unfortunately quite infeasible (they fall under the hat of "define all the behaviors"), and the talk I linked in the post goes into why: [VIDEO] (I'm the post's author.)
"goes into". [VIDEO] You mean this slide of the Joker with title "Do I really look like the kind of language that would define its behavior?" As far as I can tell there is no reason LLVM couldn't compile a document like "On x86 division by zero throws a #DE exception. On ARM division by zero returns zero. On x86 shifting by more than 32 bits shifts by (cnt mod 32) bits. On ARM shifting by more than 32 bits clears all bits. [...]" that specifies all undefined behavior. The current "nasal demons" behavior is preferred in the name of performance on some SPEC benchmarks that nobody was asking for, screwing OS writers and anyone who expected sane language semantics.
I know you're just trashing C UB because that's fashionable, but if you really think about it there are two popular options, and you can't have both at the same time: a) UB enables valuable optimizations and is important to keep (or even add) when performance matters b) UB makes the language unusable/insecure to anyone but genius level experts and should be avoided Whenever someone (including famous/relevant people like Dennis Ritchie [0], DJ Bernstein [1], or Linus Torvalds [2]) tries to suggest cleaning up, removing, or simply not adding new cases of undefined behavior in C/C++, the optimization experts come running from the other room screaming about how important it is that "signed integer overflow must be undefined" [3] or else things will run a percent more slowly (signed overflow being just one example of UB). Also there are people who suggest adding new UB to Rust [4]. So really, either Rust is significantly slower than C because Rust doesn't have the UB you're criticizing, or C could be a cleaner language without compromising on speed and the compiler writers and standards committees are wrong. You choose, but both options are considered heresy. [0] [LINK] [1] [LINK] [2] [LINK] [3] [VIDEO] [4] [LINK]
One man's "broken code produced by the compiler" is another man's "excellently optimized code by the compiler". Where to draw the line is not always clear, but here's a very clear-cut example[1] where emitting a warning would be bad. If you don't want to watch the video, it's basically this: - the code technically contains undefined behavior, but it will never be actually triggered by the program - changing the code to remove undefined behavior forces the compiler to emit terrible code Making the compiler yell at the programmer in this case would be terrible, but it's clearly a consequence of what you're asking. [1] [VIDEO]
> Thus, the compiler theoretically has the liberty to emit whatever it wants for this program. In theory, sure. In practice, what does it do? We can look and see.[0][1][2] (I don't know of any compiler that emits garbage in the example you listed). For some reason, there's this sentiment that's arisen that treats undefined behavior as some sort of bugaboo that's capable of anything and everything (including summoning nasal demons). Here's a question that I can't find an answer to. Is machine code well-defined (as in, can it contain undefined behavior)? If the answer is yes, then all Rust programs can also contain undefined behavior, because they eventually turn into machine code after all. If the answer is no, then that means once a C++ program is compiled into machine code, the executable is a well-defined program. This whole nasal demon garbage was a good hyperbole at the time to explain that, yes, undefined behavior is bad . But it's been taken to this extreme where people will use arguments like this to try and convince others that if your program has undefined behavior, then it could summon a nasal demon, because who knows what could happen. In reality, that won't happen. In reality, a signed integer overflow can result in a meaningless program, or a security vulnerability, or many other bad things, but it doesn't mean that the compiled machine code is all of a sudden inventing new instructions at random that the CPU will happily accept. It doesn't mean that your program
ISO C99 actually defines multiple types of deviating behaviour. What you're describing is closer to implementation-defined behaviour than anything else. The three behaviours relevant in this discussion, from section 3.4: 3.4.1 implementation-defined behavior unspecified behavior where each implementation documents how the choice is made EXAMPLE An example of implementation-defined behavior is the 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 Possible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). An example of undefined behavior is the behavior on integer overflow. 3.4.4 unspecified behavior behavior where this International Standard provides two or more possibilities and imposes no further requirements on which is chosen in any instance An example of unspecified behavior is the order in which the arguments to a function are evaluated. K&R seems to also mention "undefined" and "implementation-defined" behaviour on several occasions. It doesn't specify what is meant by undefined behav