15

Once in a while C++ code will not work when compiled with some level of optimization. It may be compiler doing optimization that breaks the code or it may be code containing undefined behavior which allows the compiler to do whatever it feels.

Suppose I have some piece of code that breaks when compiled with higher optimizations level only. How do i know if it's the code or the compiler and what do I do if it's the compiler?

10
  • 45
    Most likely its you.
    – littleadv
    Sep 26, 2011 at 7:54
  • 10
    @littleadv, even the recent versions of gcc and msvc are full of bugs, so I would not be so sure.
    – SK-logic
    Sep 26, 2011 at 7:57
  • 4
    You have all warnings enabled?
    – user1249
    Sep 26, 2011 at 11:18
  • 3
    FWIW: 1) I try not to do anything tricky that might tempt the compiler to mess up, 2) the only place the optimization flags matter (for speed) is in code where the program counter spends a significant fraction of its time. Unless you're writing tight cpu loops, in many applications the PC spends essentially all of its time deep in libraries, or in I/O. In that kind of app, the /O switches don't help you at all. Sep 26, 2011 at 15:33
  • 1
    Can you check with a different compiler? That would be a proof that your c++ is fine and could point towards a compiler issue.
    – Francesco
    Jun 27, 2016 at 5:34

11 Answers 11

21

I would say it is a safe bet that, in the vast majority of the cases, it is your code, not the compiler, that is broken. And even in the extraordinary case when it is the compiler, you are probably using some obscure language feature in an unusual way, for which the specific compiler is not prepared; in other words, you could most likely change your code to be more idiomatic and avoid the weak spot of the compiler.

At any rate, if you can prove that you found a compiler bug (based on the language spec), report it the compiler developers, so that they may get it fixed some time.

10
  • @SK-logic, fair enough, I have no statistics to back it. It is based on my own experience, and I admit I have rarely stretched the limits of the language and/or the compiler - others may do it more often. Sep 26, 2011 at 8:10
  • (1) @SK-Logic: Just found a C++ compiler bug, same code, tried on one compiler and works, tried in another it brokes.
    – umlcat
    Sep 26, 2011 at 18:02
  • 9
    @umlcat: most likely it was your code depending on unspecified behavior; on one compiler it matches your expectations, on another it doesn't. that doesn't means it's broken.
    – Javier
    Sep 26, 2011 at 18:54
  • @Ritch Melton, have you ever used LTO?
    – SK-logic
    Sep 27, 2011 at 6:50
  • 1
    I agree with Crashworks, when talking about game consoles. It's not unusual at all to find esoteric compiler bugs in that specific situation. If you're targetting normal PCs, though, using a heavily-used compiler, then it's very unlikely that you'll bump into a compiler bug that nobody's seen before. Jan 14, 2012 at 8:00
14

Just as usual, as with any other bugs: perform a controlled experiment. Narrow down the suspicious area, turn off the optimisations for everything else and start varying the optimisations applied to that chunk of code. Once you get a 100% reproducibility, start varying your code, introducing things that might break certain optimisations (e.g., introduce possible pointer aliasing, insert external calls with potential side effects, etc.). Looking at the assembly code in a debugger might help as well.

4
  • might help with what? If it is a compiler bug - so what?
    – littleadv
    Sep 26, 2011 at 8:04
  • 2
    @littleadv, if it is a compiler bug, you can either try to fix it (or just report it properly, in full detail), or you can find out how to avoid it in future, if you're doomed to keep using this version of your compiler for a while. If it's something with your own code, one of the numerous C++-ish borderline issues, this kind of scrutiny also helps in fixing a bug and avoiding its kind in future.
    – SK-logic
    Sep 26, 2011 at 8:07
  • So as I said in my answer - other than reporting, there's no much difference in treatment, regardless of whose fault it is.
    – littleadv
    Sep 26, 2011 at 8:09
  • 3
    @littleadv, without understanding the nature of a problem you're likely to face it again and again. And there is often a possibility to fix a compiler on your own. And, yes, it is not "unlikely" at all to find a bug in a C++ compiler, unfortunately.
    – SK-logic
    Sep 26, 2011 at 8:12
10

Examine the assembly code that resulted and see if it does what your source is calling for. Remember that the odds are very high that it's really your code at fault in some non-obvious fashion.

1
  • 2
    This is really the only answer to this question. The compilers job, in this case, is to get you from C++ to assembly language. You think it is the compiler...check the compilers work. It is that simple.
    – old_timer
    Jan 14, 2012 at 5:59
8

In over 30 years of programming, the number of genuine compiler (code generation) bugs I've found is still only ~ 10. The number of my own (and other people's) bugs I've found and fixed in the same period is probably > 10,000. My "rule of thumb" then is that the probability of any given bug being due to the compiler is < 0.001.

2
  • 1
    You're lucky. My average is about 1 really bad bug a month, and minor borderline issues are a way much more often. And the higher optimisation level you're using, the higher are compiler error chances. If you're trying to use -O3 and LTO, you'd be very lucky not to find a couple of them in no time. And I only count here the bugs in release versions - as a compilers developer, I'm facing much much more of problems of that kind in my work, but that does not count. I just know how easy it is to screw up a compiler.
    – SK-logic
    Sep 26, 2011 at 10:00
  • 2
    25 years and I also have seen very many. The compilers are getting worse every year.
    – old_timer
    Jan 14, 2012 at 5:57
5

I started writing a comment and then decided its too long and too much to the point.

I would argue that it is your code that is broken. In the unlikely event that you've discovered a bug in the compiler - you should report it to the compiler developers, but that's where the difference ends.

The solution is to identify the offending construct, and refactor it so that it would do the same logic differently. That would most likely solve the issue, whether the bug is on your side or in the compiler.

5
  1. Re-read your code thorougly. Make sure you aren't doing things with side-effects in ASSERTs, or other debug (or more general, configuration) specific statements. Also remember that in a debug build memory gets initialized differently - telltale pointer values you can check here: Debugging - Memory Allocation Representations. When running from within Visual Studio you are nearly always using the Debug Heap (even in release mode) unless you explicitly specify with an environment variable that this is not what you want.
  2. Check your build. It's common to get problems with complex builds in other places than the actual compiler - dependencies often being the culprit. I know that "have you tried completely rebuilding" is almost as infuriating an answer as "have you tried reinstalling windows", but it often does help. Try: a) Rebooting. b) Deleting all your intermediate and output files MANUALLY and rebuilding.
  3. Look through your code to check for any potential locations where you might be invoking undefined behavior. If you've been working in C++ for a while, you'll know there are some spots where you think "I'm not ENTIRELY sure I'm allowed to assume that..." - google it or ask here about that particular type of code to see whether it is undefined behavior or not.
  4. If that still doesn't seem to be the case, generate preprocessed output for the file that is causing the problems. An unexpected macro expansion can cause all kinds of fun (I am reminded of the time a colleague decided a macro by the name of H would be a good idea...). Examine the preprocessed output for unexpected changes between your project configurations.
  5. Last resort - now you really are in compiler bug land - look at the assembly output. This might take some digging and fighting just to get a handle on what the assembly is actually doing, but it's actually quite informative. You can use the skills you pick up here to evaluate micro-optimizations as well, so all is not lost.
1
  • +1 for "undefined behavior." I've been bitten by that one. Wrote some code that depended on int + int to overflow exactly as if it compiled down to a hardware ADD instruction. It worked just fine when compiled with an older version of GCC, but not when compiled with the newer compiler. Apparently the nice people at GCC decided that since the result of an integer overflow is undefined, their optimizer could operate under the assumption that it never happens. It optimized an important branch right out of the code. Jun 27, 2016 at 17:11
2

If you want to know whether it is your code or the compiler, you have to perfectly know the specification of C++.

If doubt persists, you have to perfectly know x86 assembly.

If you are not in the mood of learning both to the perfection, then it is almost certainly an undefined behaviour that your compiler resolves differently depending on optimization level.

1
  • (+1) @mouviciel: It also depends if the feature its supported by the compiler, even, if its in the specification. I have a weird bug with gcc. I declare a "plain c structure" with a "function pointer", its allowed in the specification, but works in some situations, and doesn't in another.
    – umlcat
    Sep 26, 2011 at 18:07
1

Getting a compile error on standard code or an internal compile error is more likely than optimizers being wrong. But i have heard of compilers optimizing loops incorrectly forgetting some side effects a method cause.

I have no suggestions on how to know if its you or the compiler. You may try another compiler.

One day i was wondering if it was my code or not and someone suggested valgrind to me. I spent the 5 or 10mins to run my program with it (i think valgrind --leak-check=yes myprog arg1 arg2 did it but i played with other options) and it immediately showed me ONE line that is ran under one specific case which was the problem. Then my app ran smoothly ever since with no weird crashes, errors or strange behavior. valgrind or another tool like it is a good way to know if its your code.

Side note: I once wondered why the performance of my app sucked. It turned out all of my performance problems was in one line as well. I wrote for(int i=0; i<strlen(sz); ++i) {. The sz was a few mb. For some reason the compiler ran strlen every time even after optimization. One line can be a big deal. From performances to crashes

1

An increasingly common situation is that compilers break code written for dialects of C that supported behaviors not mandated by the Standard, and allowed code targeting those dialects to be more efficient than strictly-conforming code could be. In such a case, it would be unfair to describe as "broken" code which would be 100% reliable on compilers that implemented the target dialect, or to describe as "broken" the compiler which processes a dialect that does not support the required semantics. Instead, the problems stem simply from the fact that the language processed by modern compilers with optimizations enabled is diverging from dialects which used to be popular (and are still processed by many compilers with optimizations disabled, or by some even with optimizations enabled).

For example, a lot of code is written for dialects that recognize as legitimate a number of pointer aliasing patterns not mandated by gcc's interpretation of the Standard, and makes use of such patterns to allow a straightforward translation of the code to be more readable and efficient than would be possible under gcc's interpretation of the C Standard. Such code may not be compatible with gcc, but that doesn't imply that it's broken. It simply relies upon extensions that gcc only supports with optimizations disabled.

3
  • Well, sure there is nothing wrong with coding C standard X + extensions Y and Z, as long as that gives you significant advantages, you know you did that, and you thoroughly documented it. Sadly, all three conditions are normally not met, and thus it's fair to say the code is broken. Jun 27, 2016 at 12:12
  • @Deduplicator: C89 compilers were promoted as being upwardly-compatible with their predecessors, and likewise for C99, etc. While C89 imposes no requirements on behaviors which had previously been defined on some platforms but not others, upward compatibility would suggest that C89 compilers for platforms that had regarded behaviors as defined should continue to do so; the rationale for the promotion of short unsigned types to signs would suggest that the authors of the Standard expected compilers to behave that way whether or not the Standard mandated it. Further...
    – supercat
    Jun 27, 2016 at 15:28
  • ...strict interpretation of the aliasing rules would throw upward compatibility out the window and make many kinds of code unworkable, but a few slight tweaks (e.g. identifying some patterns where cross-type aliasing should be expected and therefore permissible) would solve both problems. The whole stated purpose of the rule was to avoid requiring compilers to make "pessimistic" aliasing assumptions, but given "float x", should a presumption that "foo((int*)&x)" might modify x even if "foo" doesn't write to any pointers of type 'float*" or "char*" be considered "pessimistic" or "obvious"?
    – supercat
    Jun 27, 2016 at 15:41
0

Isolate the problematic spot and compare the observed behavior to what should happen according to the language spec. Definitely not easy, but that's what you have to do to know (and not just assume).

I probably wouldn't be that meticulous. Rather, I would ask the compiler maker's support forum / mailing list. If it's really a bug in the compiler, then they might fix it. Probably it would be my code anyway. For example, language specs regarding memory visibility in threading can be quite counterintuitive, and they could become apparent only when using some specific optimization flags, on some specific hardware (!). Some behaviour could be undefined by the spec, so it could work with some compiler / some flags, and not work with some other, etc.

0

Most likely your code has some undefined behavior (as others explained, you are much more likely to have bugs in your code than in the compiler, even if C++ compilers are so complex that they do have bugs; even the C++ specification has design bugs). And UB can be here even if the compiled executable happens to work (by bad luck).

So you should read Lattner' blog What Every C Programmer should know about undefined behavior (most of it applies for C++11 too).

The valgrind tool, and recent -fsanitize= instrumentation options to GCC (or Clang/LLVM), should also be helpful. And of course, enable all warnings: g++ -Wall -Wextra

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.