Why is compiler optimisation breaking my code?

Viewed 119

I was having some issues with this code. It was working in debug mode but would erroneously output 0.0 for some of the csv lines in release mode. It was only when I added the preprocessor commands that turn off optimisation around line 19 that everything started to work in release mode. Does anyone have any insight into why this is the case, I've never come across this sort of behavior before.

#include <vector>
#include <complex>
#include <fstream>

size_t constexpr kBins = 64;
double constexpr kFrequency = 16.0;

double Triangle(double const bin)
{
    return abs(bin - floor(bin + 1.0 / 2.0));
}

int main()
{
    std::vector<std::complex<double>> input{kBins, {0.0, 0.0}};
    for (size_t i = 0; i < kBins; ++i)
    {
#pragma optimize("" off)
        double value = sin(2.0 * M_PI * kFrequency * Triangle(static_cast<double>(i) / kBins)) / (2.0 * M_PI * kFrequency * Triangle(static_cast<double>(i) / kBins));
#pragma optimize("" on)
        input[i] = fpclassify(value) == FP_NAN ? 1.0 : value;
    }

    std::ofstream output_file{"output.csv"};
    if (output_file.is_open())
    {
        for (size_t i = 0; i < kBins; ++i)
        {
            output_file << (static_cast<double>(i) / kBins) << ", " << input[i].real() << ", " << input[i].imag() << std::endl;
        }
        output_file.close();
    }
}
1 Answers

Your input vector is not being initialized as you expect. It always has size two, and all the strange results you observe are a result of Undefined Behaviour due to reading the vector out of bounds. You could have found this with your debugger, or by checking against input.size() or using input.at(i).

It turns out that size_t is convertible to std::complex<double>, and that this line:

std::vector<std::complex<double>> input{kBins, {0.0, 0.0}};

Is constructing a vector with two elements, even though kBins = 64! It appears the vector constructor taking a std::initializer_list<std::complex<double>> is being chosen and that kBins has been implicitly converted to std::complex<double>.

One way to get around this is to initialize your vector differently, using the auto keyword to avoid the Most Vexing Parse and using parentheses instead of curly braces to avoid accidentally passing a std::initializer_list:

auto input = std::vector<std::complex<double>>(kBins, {0.0, 0.0});`

With this change, the (range-checked) code runs without errors.

Related