Search code examples
c++gccconstantscompiler-optimizationundefined-behavior

Example of undefined behaviour involving the use of const_cast


For illustrative purposes, I have been trying to find an example, by using gcc, where the output of a program is different with and without optimization enabled (with and without -O3). The purpose of finding such example is to show how optimizations could make an apparently correct program behave different after optimizations have been active if the code contains undefined behaviour.

I have been trying different "combos" of the following program:

// I have tried defining blind in this and in a separate module. The result is the same.
void blind(int const* p) { ++*const_cast<int*>(p); }

#include <iostream>

int constant() { return 0; }

int main()
{
    int const p = constant();
    blind(&p);
    std::cout << p << std::endl;
    return 0; 
}

I was expecting that, without optimizations enabled, this program will show 1, but with optimizations enabled (-O3) it will show 0 (by replacing std::cout << p by std::cout << 0 directly), but that's not the case. If I replace the initialization by int const p = 0, it will print 0 with and without optimizations enabled, and so the behaviour is again the same.

I have tried different alternatives like doing arithmetic operations (expecting the compiler to prefer to "pre-compute" the value or something), calling blind several times, etc. But nothing works.

  • I would like to find a variation of the program above whose behaviour change when activating optimizations.
  • Or... another different example that could help to illustrate that optimizations can change the observable behaviour of a program if such program contains undefined behaviour.

NOTE: Preferably, one example where the program won't probably crash in the optimized version.


Solution

  • A nice and very simple case that matches the kind of example I was looking for is the following:

    #include <iostream>
    #include <climits>
    
    bool check(int i)
    {
        int j = i + 1;
        return j < i;
    }
    
    int main()
    {
        std::cout << check(INT_MAX) << std::endl;
        return 0;
    }
    

    Without optimizations enabled, check returns 1, because overflow did happen. With optimizations enabled, even with -O1, check returns 0.

    I started with:

    #include <iostream>
    #include <climits>
    
    bool check(int i)
    {
        return i + 1 < i;
    }
    
    int main()
    {
        std::cout << check(INT_MAX) << std::endl;
        return 0;
    }
    

    Since signed integer overflow is UB, the compiler directly returned 0 without performing the actual comparision even without optimizations enabled: enter image description here

    Since the behaviour was still the same with and without optimizations, I decided to move the calculation of i + 1 to a new variable j:

    bool check(int i)
    {
        int j = i + 1;
        return j < i;
    }
    

    Now, the compiler, in a non-optimized build, is forced to actually calculate j so the variable can be inspected with a debugger, and the comparision is actually performed, and that's why it returns 1.

    However, with -O1, the compiler translated check to its equivalent form return i + 1 < i, which becomes return 0 as in the previous variation of the program.