17 ms·
I can't be the only one that 99.9% of the time doesn't care _one iota_ about these mythical optimizations that compilers can introduce by exploiting undefined b
by armitron 8y ago
I can't be the only one that 99.9% of the time doesn't care _one iota_ about these mythical optimizations that compilers can introduce by exploiting undefined behavior. I just want to write straightforward, predictable code that tries its best to be safe but for one reason or another have to stick with C.
Is there a guide/reference on how to disable these optimizations in modern compilers? A list of GCC/Clang arguments that disable as much of this as possible would be greatly appreciated. I've seen a lot of posts and articles discussing C undefined behavior but almost nothing describing how to counter it.
- pcwalton 8y agoSure. Compile at -O0. A huge number of seemingly-trivial optimizations depend on assuming that undefined behavior will never happen. The number of optimizations that don't depend on UB in any way is quite small. For example, if you want to get pedantic about it, even automatic promotion of local variables to registers is exploiting undefined behavior—who's to say you didn't have a pointer that just happened to point to one of them?
- kazinator 8y agoOf course you can have a pointer to a local variable, and that variable can still spend portions of its lifetime in a register. What won't necessarily work as expected is a shadily obtained pointer to a local variable, like by displacing &i to try to point to the same location as &j. If &j doesn't occur anywhere (or does occur, but in a way that is optimized away), j may not even have a location.
- jcranmer 8y agoHow do you reflect the fact that pulling a pointer to a value "out of thin air" may not make any sense without either banning it entirely or defining some step of the process to be undefined behavior?
- esrauch 8y agoMost languages have no concept of undefined behavior and they still have compilers and runtimes that can do a huge amount of optimizations.
- eddyb 8y agoThe languages that you describe must look nothing like C (other than, ironically, syntax), and must have no untyped direct memory access pointer feature at all, which usually means they rely on a GC instead for memory safety.
- zokier 8y ago> and must have no untyped direct memory access pointer feature at all Can you expand on why you feel this must be the case? I'm thinking for example deference of pointer could be defined to be equivalent of platform-specific memory load instruction. It wouldn't be memory safe and would segfault like C, but it still wouldn't bring up nasal demons like C UB.
- pcwalton 8y agoIf every source-language load must result in a memory load, then you at the very least killed scalar replacement of aggregates (a critical optimization), and, depending on how you define it, you might have killed register allocation too.
- vyodaiken 8y agoI'd like to see some actual evidence of some significant optimization that depends on UB.
- eddyb 8y agoSee this sibling thread https://news.ycombinator.com/item?id=17189666 https://news.ycombinator.com/item?id=17189666 - typically anything touching memory needs invariants to be optimized, which in languages that can directly manipulate memory means there's also UB (code that can't be statically proven not to break those invariants).
- vyodaiken 8y agoyou don't need UB for invariant optimization. What UB permits the compiler to do is INCORRECTLY assume invariance. This permits the compiler to "optimize" badly written C code in a way that silently changes its function - which I don't call an optimization.
- xenadu02 8y agoI’m not sure you’ve though this through. Any C function that manipulates pointers (or arrays) can alias those pointers, type pun, and otherwise touch the same block of memory through different paths and/or treating the bits as different types. Vast areas of optimization are completely closed to you if you want code to be resilient in the face of aliasing. You should sit down with pen and paper to figure out how to optimize a simple function while preserving invariants but without UB. It would be very illuminating.
- vyodaiken 8y agoIt's really irritating that people take this condescending approach. C is harder to optimize than language that e.g. do not have pointers. That is a tradeoff the language designers chose because they wanted C programmers to be able to type pun, for example. This is why C89's only limit on type punning was memory alignment. The intent of "restrict" was to enable some aliasing optimizations via opt-in. So, sure, things that are not invariant in C are not available for invariant optimization. C permits aliasing. So it calls for smarter compiler techniques. If you want a language that forbids aliasing, use one that does and then call out to C libraries. But breaking C to allow CS220 "clever optimizing tricks" to be easily used is bad engineering. Essentially, what you are saying is, "elimination of invariants is a good optimization, so pretending C has more invariants than it does is a good thing. " So maybe come up with a real example and stop hand waving.
- loup-vaillant 8y ago> Sure. Compile at -O0. Hmm, nope. Sure, it will save you from much UB. But GCC and Clang have idiotic front ends. On purpose. They subscribe to the idea that the optimizer phase should clean up the front end mess. They're not entirely wrong, since it does simplify the front end, and does not really make the rest any more complex. It does make it slower, though. Long story short, those compilers don't generate reasonable code by default. You need at least -O1.
- glandium 8y agoI was recently surprised to find that GCC, at -O0, does some level of optimization. At least it does for multiplications. https://godbolt.org/g/ekTkBm https://godbolt.org/g/ekTkBm Edit: the irony is that if you do compile with optimizations, it does a different optimization.
- umanwizard 8y agoWhat do you mean by "reasonable" ? Can you give an example of code that is compiled in a more obvious, straightforward way with O1 than O0 ?
- loup-vaillant 8y agoMy knowledge of this is only second hand, so I don't have any specific example. I believe Chandler Carruth explains this well on one of his LLVM talks (that is, how and why the front end emits crappy intermediate code, before the optmiser kicks in).
- xorblurb 8y agoThat's, given the question, is a really moronic answer. There are means to apply the modern approach of optimizations beyond O0 on C without importing all kind of UB from the language level. You just have to actually proove the properties you want to rely on, instead of relying on wishful "UB => authorized by the standard => programmer fault if anything goes bad" thinking. And promoting local variable to registers CERTAINLY NOT depends on language level UB. It would be permitted by the as-if general rule even if anything prevented it to happen in the first place, which is not the case. You don't have to have an address if nobody wants it and random pointers haver never been required to allow access to all objects, especially those who might never have an address at all. Plus nobody ever expected that anyway. People expect 2s complement. Or at least something that can not result in nasal daemons, and given C history, something that matches what the processor does. So 2s complements is at least not utterly stupid. So conflating the two is dishonest to the highest point -- except maybe if the only intended audience of the C language is now experts who e.g. write compilers. What a bright future this would be. Hell, we dropped the hypothetical flat memory model even without strict aliasing for maybe 20 years (and probably 30, to be honest), and this had NEVER caused the kind of issues we are talking about. So don't pretend it did, just to dismiss the real issues. So ok even then it was actually probably informal as hell and in some ways worse for experts, but the amount of exploited UB was also WAY smaller. Quantity matters in this area. And context too. Do you want secure OR fast embedded systems? I would prefer reasonably secure and reasonably fast. Certainly NOT fast to execute and exploit, or more probably fast to crash pathetically. You know very well that compiling in O0 is not going to happen in prod on tons on projects. Don't dismiss real concerns with false "solutions", especially when mixed with proofs of your misunderstanding of the situation.
- comex 8y ago> You don't have to have an address if nobody wants it The spec says otherwise: > An object exists, has a constant address, and retains its last-stored value throughout its lifetime. [C11 6.2.4.2] But that's more of a pedantic detail. More pragmatically, you do have a point. It is indeed possible to build a C compiler that has no "undefined behavior", but only unspecified behavior, as long as the program doesn't actually violate memory safety by, say, writing to some random address it can't prove it has permission to write. For example, guessing the stack slot used for a variable and overwriting it pretty much has to be undefined behavior – it's hard to optimize anything if variables can randomly change their values without being referenced. But that's okay, because overwriting random memory is inherently unsafe without obtaining a guarantee of what that memory will be used for. On the other hand, reading from random stack memory could be unspecified. A particular stack slot might be used for a variable, a temporary expression, or nothing at all, so it's unspecified what you might find there. But the compiler will always generate a single, real load instruction, without making any assumptions about aliasing that it can't prove; thus, you'll never get logical impossibilities like "x + 1 > x". And such a compiler could definitely produce code that's better optimized than -O0 – because -O0 is a very, very low bar (it doesn't even do register allocation, in the compilers I've seen). But I expect it would do substantially worse than a modern compiler's -O2 even on average code, with a lot of little missed optimizations that add up. (Though if you're using -fno-strict-aliasing, you're probably already eating a decent percentage of that penalty.) And in the worst cases, like tight loops that can be autovectorized only by taking advantage of undefined behavior, it might be only a fraction of the speed of the better-optimized version. Still, it might be an interesting project, especially if you could formalize the "no undefined behavior" guarantee.
- bokglobule 8y agoAmen. IMO the root of all evil is compiler "optimization" of code. If you really, really need the wee bit of extra performance through optimization, hire an expert at ass'y language and optimize the bit that matters. Rarely is this the case, especially with business apps. I've seen my fair share of bugs from the compiler incorrectly hoisting variables from loops, mis-using registers, etc. I prefer to turn off compiler optimization as one of the first steps in a new project, but that's me.
- anyfoo 8y agoC is nowadays foremost a system programming language. Try compiling your operating system with -O0 and see if you still don’t need those optimizations. You’d be surprised.
- kazinator 8y agoIf the project will eventually be compiled with optimizations in production, it's probably better to hit those problems earlier than later.
- adwn 8y ago> If you really, really need the wee bit of extra performance through optimization, hire an expert at ass'y language and optimize the bit that matters. Rarely is this the case, especially with business apps. If execution efficiency is not a concern and you're using C, then you're most likely using the wrong language. Especially for "business apps".
- AnimalMuppet 8y agoYou can care enough about efficiency to use C, and still not care about the last 0.1% of efficiency. You can especially not care about the last 0.1% of efficiency in 99% of your code.
- jcranmer 8y agoIf you're compiling C with -O0 (as OP implies)... it's not just the last 0.1% of efficiency you're missing out on. Modern C compilers generate really, really crappy code at -O0, and you're looking at a 10x, 20x slowdown by forgoing optimizations. At those slowdowns, using an interpreted language that lacks a JIT starts to look competitive in performance.
- umanwizard 8y agoDisabling them is trivial: compile with -O0 in clang or gcc and your code will be compiled in as straightforward a way as possible.
- rwj 8y agoUB is also a source of portability errors...
- xamuel 8y agoHere's an example where these optimizations are useful. Suppose I create a macro DEREF_AND_FREE(x) which expands to if(x)free(*x). Often times, I'll lazily use that in places where I know x isn't null. It's more readable and maintainble than splitting the macro up into two separate macros, DEREF_AND_FREE_IF_NONNULL and DEREF_AND_FREE_I_KNOW_ITS_NONNULL. The UB-based optimizations mean that I don't take the performance hit for my laziness.
- vyodaiken 8y agoThere is nothing to prevent the compiler from deducing that x is non-null without UB. What UB permits is for the the compiler to deduce that x is not null in the absence of information. so : if(x)f(x) ...code ... if(x)g(x) doesn't need UB but y = x-> n ..... code if(x)g(x) - the compiler uses UB to remover the check. This will be especially horrible when LTO becomes more prevalent.
- deleted 8y ago[deleted]