为什么在clang中使用-O2或更高的优化会破坏这段代码?

5

我查看了网站上的类似问题,但没有找到与我的情况相匹配的内容。这是我试图运行的代码(需要C++14):

#include <iostream>
#include <chrono>
#include <thread>
using namespace std;

class countdownTimer {
public:
    using duration_t = chrono::high_resolution_clock::duration;

    countdownTimer(duration_t duration) : duration{ duration }, paused{ true } {}

    countdownTimer(const countdownTimer&)               = default;
    countdownTimer(countdownTimer&&)                    = default;
    countdownTimer& operator=(countdownTimer&&)         = default;
    countdownTimer& operator=(const countdownTimer&)    = default;

    void start() noexcept {
        if (started) return;
        startTime = chrono::high_resolution_clock::now();
        endTime = startTime + duration;
        started = true;
        paused = false;
    }

    void pause() noexcept {
        if (paused || !started) return;
        pauseBegin = chrono::high_resolution_clock::now();
        paused = true;
    }

    void resume() noexcept {
        if (!paused || !started) return;
        auto pauseDuration = chrono::high_resolution_clock::now() - pauseBegin;
        startTime += pauseDuration;
        endTime += pauseDuration;
        paused = false;
    }

    double remainingSeconds() const noexcept {
        auto ret = double{ 0.0 };
        if (!started) ret = chrono::duration_cast<chrono::duration<double>>(duration).count();
        else if (paused) ret = chrono::duration_cast<chrono::duration<double>>(duration - (pauseBegin - startTime)).count();
        else ret = chrono::duration_cast<chrono::duration<double>>(duration - (chrono::high_resolution_clock::now() - startTime)).count();
        return (ret < 0.0) ? 0.0 : ret;
    }

    duration_t remainingTime() const noexcept {
        auto ret = duration_t{ 0ms };
        if (!started) ret = chrono::duration_cast<duration_t>(duration);
        else if (paused) ret = chrono::duration_cast<duration_t>(duration - (pauseBegin - startTime));
        else ret = chrono::duration_cast<duration_t>(duration - (chrono::high_resolution_clock::now() - startTime));
        return (ret < 0ms) ? 0ms : ret;
    }

    bool isPaused() const noexcept { return paused; }

    bool hasFinished() const noexcept { return remainingTime() == 0s; }

    void reset() noexcept {
        started = false;
        paused = true;
    }

private:
    chrono::high_resolution_clock::time_point startTime;
    chrono::high_resolution_clock::time_point endTime;
    chrono::high_resolution_clock::time_point pauseBegin;
    duration_t duration;
    bool paused;
    bool started;
};

int main() {
    countdownTimer timer(10s);
    timer.start();

    while (!timer.hasFinished()) {
        cout << timer.remainingSeconds() << endl;
        this_thread::sleep_for(1s);
    }
}

这是我为我的一个项目编写的一个简单的倒计时器类。在main()中的客户端代码非常容易理解,它应该输出从10到0的倒计时,然后退出程序。没有任何优化或-O/-O1,它就可以完美地实现这个功能。

10
8.99495
7.98992
6.9849
5.97981
4.9748
3.96973
2.9687
1.9677
0.966752
Program ended with exit code: 0

但是,如果我把优化提升到 >= -O2,程序将一直输出10,并永远运行。倒计时根本不起作用,它被卡在起始值处。我正在使用最新的OS X上的Xcode。clang --version命令显示Apple LLVM版本7.3.0(clang-703.0.31)。奇怪的是,我的代码不包含任何奇怪的自编写循环、未定义的行为或类似的内容。它几乎只是标准库的调用,所以很奇怪优化会破坏它。有任何想法吗?注:我还没有在其他编译器上尝试过,但我即将尝试。我将在问题中更新这些结果。

1
优化级别不应该破坏你的代码行为,除非你在某个地方引入了未定义的行为。 - πάντα ῥεῖ
2
尝试重现问题的人请注意:此代码需要使用“-std=c++14”或更高版本进行编译。 - zwol
@adam10603 使用一个好的性能分析或静态代码分析工具来查找潜在的源头。 - πάντα ῥεῖ
@πάνταῥεῖ 我有点熟悉 Linux 上的 perf,我可以尝试一下。 - notadam
@adam10603 在Linux上尝试使用Valgrind。 - πάντα ῥεῖ
显示剩余4条评论
2个回答

9

bool started没有被初始化。

如果你将其初始化为false,它可以在使用-O2的情况下工作:

实时示例

您可以使用未定义行为检测器找到此类错误:

$ g++ -std=c++14 -O2 -g -fsanitize=undefined -fno-omit-frame-pointer main.cpp && ./a.out

main.cpp:18:9: runtime error: load of value 106, which is not a valid value for type 'bool'

6

错误在于您的构造函数:

 countdownTimer(duration_t duration)
 : duration{ duration }, paused{ true } {}

您忘记初始化started。当您调用start()时,这会触发未定义的行为。

我方便访问的所有clang版本都无法诊断此错误,但Linux上的GCC版本5和6(我不再在我的Mac上拥有GCC)可以:

$ g++ -O2 -Wall -Wextra -std=c++14 test.cc
test.cc: In function ‘int main()’:
test.cc:18:13: warning: ‘*((void*)& timer +33)’ is used uninitialized in this function [-Wuninitialized]
         if (started) return;
             ^~~~~~~
test.cc:74:20: note: ‘*((void*)& timer +33)’ was declared here
     countdownTimer timer(10s);
                    ^~~~~

我的Xcode副本似乎有点过时,带有Apple LLVM版本7.0.2(clang-700.1.81); 它不会改变程序在-O2下的行为。如果您打开警告,您的clang可能会诊断此错误。 我已向GCC提交了一个关于诊断中的IR难以理解的错误报告

啊...我没有初始化它,因为我在start()中给它赋了一个值,但是我后来在start()的开头插入了检查,然后我没有再考虑过这个问题。 - notadam
为什么在较低的优化级别下这不会引起任何问题? - notadam
无论是哪个优化通道暴露了问题,在较低的优化级别下都没有启用。如果需要更详细的回答,就需要深入挖掘中间阶段的调试转储,而我不知道如何使用clang进行这样的操作(而且我也没有适合的clang版本来执行此操作)。 - zwol

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