Search code examples
cundefined-behavior

Does any popular compiler exploit undefined behaviors when optimization is off?


It is claimed in this blog that:

  • Undefined behavior only "happens" at high optimization levels like -O2 or -O3.
  • If I turn off optimizations with a flag like -O0, then there's no UB.

are both false. I'm wondering if there's any real-world showcase for the claim.

For example, n << 1 triggers UB when n<0. For the following function:

void foo(int n) {
  int t = n<<1;
  if (n>=0)
    nuke();
}

the compiler could compile it cautiously:

void foo(int n) {
  int t = n>=0 ? (n*2) : error("lshift negative int");
  if (n>=0)
    nuke();
}

or normally:

void foo(int n) {
  int t = n*2;
  if (n>=0)
    nuke();
}

or optimize it aggresively:

void foo(int n) {
  // unused
  // int t = n<<1;

  // always true, otherwise UB
  // if (n>=0)
    nuke();
}

Is there any modern popular compiler like gcc/clang that behave in the last way, where some UB not only causes unexpected behavior locally at that statement, but could also be exploited purposely (not considering buffer-overflow attack etc) and pollute the control flow globally, even when -O0 is specified?

Put it simply, are all UBs practically somehow implementation-defined under -O0?

== EDIT ==

The question is not if those claims are theoretically false or nonsensical (because they are). It's whether there's realworld showcase. As @nate-eldredge has rephrased it in the comment:

given some piece of code that is formally UB, a real-life non-optimizing compiler produces results which are particularly surprising (in the way described above), even to a reasonably knowledgeable programmer?


Solution

  • First let's make it absolutely clear that the blog is correct:

    Both statements are false

    As user @O___________ also writes in this answer https://stackoverflow.com/a/76720573/4386427 Undefined Behavior is a property of the C source code. No matter what a compiler does the C source code still has undefined behavior. A compiler can't change that.

    Then you ask for an example that will surprise (quote): surprising (in the way described above), even to a reasonably knowledgeable programmer

    The answer to that must be Such an example doesn't exists

    Reason: A "reasonably knowledgeable programmer" knows that it makes no sense to reason about how code with undefined behavior behaves. So a "reasonably knowledgeable programmer" will never be surprised no matter what the resulting program does.

    For "less knowledgeable programmers" there may be many examples that could be surprising. For instance:

    #include <stdio.h>
    
    int* foo(void)
    {
        int x;
        printf("%p\n", (void*)&x);
        return &x;
    }
    
    int main(void)
    {
        printf("%p\n", (void*)foo());
        return 0;
    }
    

    With gcc 12.2 I get:

    0x7ffc5981a73c
    (nil)
    

    Am I surprised? No, the code has undefine behavior so I don't expect any specific behavior.

    Would an unexperienced C programmer be surprised? Perhaps.

    Put it simply, are all UBs practically somehow implementation-defined under -O0?

    No

    "implementation-defined" is something completely different than undefined behavior. An implementation is not required to specify what it will do with code having undefined behavior. It's even allowed to do one thing on mondays, another thing on tuesdays and so on.

    "implementation-defined" behavior is something that the implementation must document so that users know what will happen. Two different implementations are allowes to do different things as long as the they document what they do. For code with undefined behavior no documentation is required.