5 ms·
I claim this is all highly unhelpful. Both the C and C++ standards explicitly permit an implementation to do something sensible in the face of undefined behavio
by to3m 11y ago
I claim this is all highly unhelpful. Both the C and C++ standards explicitly permit an implementation to do something sensible in the face of undefined behaviour, the sort of thing that a regular user of the system in question might expect. It's a bit unclear to me why compiler authors don't just do that. Undefined means exactly what it says, nothing more, but people seem to love this idea that it has to be some kind of here-be-dragons nightmare scenario.
Mandatory reading: http://blog.metaobject.com/2014/04/cc-osmartass.html http://blog.metaobject.com/2014/04/cc-osmartass.html, http://robertoconcerto.blogspot.co.uk/2010/10/strict-aliasing.html http://robertoconcerto.blogspot.co.uk/2010/10/strict-aliasin...
- mikeash 11y agoDoing "something sensible" sounds good, but it's hard to actually do. You can point out the sensible approach to specific examples, but writing the optimizer to take a "sensible approach" in all cases is hard. For every example like this, where the programmer wants a NULL check to be done even when the value "can't be NULL," there's an opposite example where the programmer wants a NULL check to be elided because the value actually can't be NULL. Taking advantage of undefined behavior isn't done because compiler writers are sadists who enjoy writing perverse implementations which rely on the letter of the law. It's done because it allows the compiler to generate better, faster code. I think a lot of the trouble is that too many programmers learn C in a completely ad-hoc, informal fashion and never take the time to actually become familiar with the fundamentals of the language. People simply don't know how the language is specified, what operations are legal and what operations aren't, why there are illegal operations that the compiler won't (and can't) warn you about, etc. C is definitely a dangerous language and is a difficult language to use well. But that's all the more reason people who plan to use it need to ensure they understand it. More mandatory reading: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html http://blog.llvm.org/2011/05/what-every-c-programmer-should-...
- andrewflnr 11y ago> It's done because it allows the compiler to generate better, faster code. Why? Why is this OK? You say doing "something reasonable" is hard, but it obviously doesn't include entirely ignoring code I wrote. That's basically saying "I see you did something weird there, but going to assume the rest of your code and ask the libraries you link to are perfectly standard-compliant, which lets me generate faster code here. I'm so smart!" Default safe, not fast, wherever possible.
- Kristine1975 11y agoC and C++ are not safe programming languages, though, no matter what the compiler does or doesn't.
- mikeash 11y agoSure it does. A big part of optimization is specializing code to be fast by removing unimportant bits. Here's a quick bad example: int computation(int *a, int *b) { if(a == NULL && b == NULL) return 0; if(a == NULL) return *b; if(b == NULL) return *a; return ...something with *a and *b...; } ...elsewhere... int a[1024]; int b[1024]; ...fetch data into a and b... int c[1024]; for(int i = 0; i < 1024; i++) c = computation(a + i, b + i); Would you want this code, compiled with full optimizations, to run slowly because it does four NULL checks every time through the loop? A good compiler will probably inline the computation and eliminate all the checks because the condition they're checking for can never happen. Is that a bad thing?
- nkurz 11y agoThis is a good example, and I think shows persuasively that it's good for a compiler to make logical assumptions about inputs when inlining code. But I think this is different than making optimizations by presuming the absence of undefined behavior when compiling the function itself. Consider a programmer who feels it's clearer to dereference a and b once, rather than writing using * a and * b in the complicated math after the preliminary checks. And because they don't want to rely on C99 or distract by opening a new block, they put the conversion at the top of the function. Oh, and the new guy decided to add the "missile launch" code here. int computation(int *ptr_a, int *ptr_b) { int val_a = *a, val_b = *b; if(ptr_a == NULL && ptr_b == NULL) return 0; if(ptr_a == NULL) return val_b; if(ptr_b == NULL) return val_a; launch_missiles(); /* OK to put this here? */ return ...something with val_a and val_b...; } I think there is a reasonable argument that there is a difference between optimizing when a code path can be shown to never occur because of constrained inputs, and optimizing because behavior supposedly can be inferred from the absence of undefined behavior. I'm fine with a literalist compiler that produces code that crashes when it immediately tries to dereference a NULL pointer, with one that produces code that does the safety checks but defers the load, but even if allowed by the spec[1] I don't think it's good behavior for a compiler to silently omit the safety checks and launch the missiles when passed a NULL. [1] Is this undefined behavior according to the spec? I consider myself an expert C programmer, and I think it's undefined in C99, but I wouldn't bet that I've correctly understood the issues for the other dialects of C, muchless C++. Is a conforming compiler "allowed" to skip the safety checks and launch the missiles? I don't know, and thus defensively wouldn't write the code like this, but it would be nice to think that long-standing mission-critical code written by others won't suddenly stop working with a new release of the optimizing compiler.
- rwmj 11y agoSurely the compiler could see that you've written some code (ie. not a macro expansion or whatever) and that the code is unreachable, and print some sort of dead code warning? That would have helped the poster, assuming he turns on and reads warnings.
- scott_s 11y agoC and C++ applications include lots and lots of headers from the systems and third party libraries. Dead code is common in such circumstances, and programmers would drown in a sea of dead code eliminated warnings.
- rwmj 11y agoBut you can still see: is the source of this code something the user has written in a .c file, or something that came from a header file or from macro expansion, and only warn about the former. Granted this is difficult, because LLVM is multi-layered, but it's not impossible in the general case.
- scott_s 11y agoI think it's extremely difficult, to the point of not being practical. When you get to the point of the compiler that it's applying these optimizations, that the code came from a .c or .h file has been long thrown away.
- to3m 11y agoHere's something that I just saw on twitter, a post from djb that I think I'll add to my mandatory reading list: https://groups.google.com/forum/#!msg/boring-crypto/48qa1kWignU/o8GGp2K1DAAJ https://groups.google.com/forum/#!msg/boring-crypto/48qa1kWi...