为什么编译器优化会破坏我的代码?

3

我在处理这段代码时遇到了一些问题。在 debug 模式下能正常工作,但在 release 模式下会错误地输出一些 csv 行的值为 0.0。只有当我添加了大约在第 19 行附近关闭优化的预处理命令后,一切才开始在 release 模式下正常工作。是否有人对此情况有任何见解?我以前从未遇到过这种行为。

#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();
    }
}

哪个编译器? - john
2
请提供一个最小可复现示例。当您感觉优化器正在破坏您的代码时,99% 的情况下是由于未定义行为,0.9% 的情况下是由于对浮点精度的错误假设。其余情况可能是编译器的错误。 - Passer By
Clang。最小可重现的示例就是删除#pragma行。 - Joshua Williams
@JoshuaWilliams ofstream的东西是无关紧要的(除非你被格式化舍去),而且你不需要64个值来看出一个是坏的。 - Passer By
将第19行的复杂表达式简化为子表达式,并添加检查以确保它们正确。同时打开最严格的编译器警告级别。通常,“我的代码在优化时不起作用”这种类型的错误意味着您在某个地方使用了未定义的值,或者在表达式(序列点之间)中修改了一个值。 - Tumbleweed53
Valgrind、-fsantize=address等工具是处理那些在发布模式下似乎“消失”的错误时的一个不错的第一步。 - Nate Eldredge
1个回答

7
你的 input 向量没有按照你的期望进行初始化。它始终具有大小为2,并且你观察到的所有奇怪结果都是由于读取超出向量范围导致的未定义行为。你可以通过调试器或检查 input.size() 或使用 input.at(i) 来发现这个问题。
事实证明,size_t 可以转换为 std::complex<double>,而这一行代码:
std::vector<std::complex<double>> input{kBins, {0.0, 0.0}};

即使 kBins = 64,也可以使用两个元素构造向量。似乎选择了一个接受 std::initializer_list<std::complex<double>>vector 构造函数,并且将 kBins 隐式转换为 std::complex<double>

解决这个问题的一种方法是以不同方式初始化您的向量,使用 auto 关键字避免最烦人的解析,并使用括号而不是花括号来避免意外传递 std::initializer_list

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

有了这个改变,经过范围检查的代码可以无错误运行


你不会因为 {0.0, 0.0} 而得到令人烦恼的解析。 - Passer By
@PasserBy 好眼力,尽管了解 MVP 并知道如何避免它是一个好习惯。 - alter_igel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接