【问题标题】:Why does -O2 or greater optimization in clang break this code?为什么 clang 中的 -O2 或更大的优化会破坏此代码?
【发布时间】:2016-12-12 19:40:16
【问题描述】:

我在网站上查看了类似的问题,但在这里找不到任何符合我的情况的内容。这是我要运行的代码(需要 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 --versionApple LLVM version 7.3.0 (clang-703.0.31)

奇怪的是我的代码不包含任何奇怪的自写循环、未定义的行为或类似的东西,它几乎只是标准库调用,所以很奇怪优化会破坏它。

有什么想法吗?

PS:我还没有在其他编译器上尝试过,但我即将尝试。我会用这些结果更新问题。

【问题讨论】:

  • 优化级别实际上不应破坏代码的行为,除非您在某处引入了未定义的行为。
  • 是的,但我真的不知道它可能在哪里。如果是这样,那一定是我没有注意到的非常微妙的东西。
  • 对试图重现该问题的人的注意事项:此代码需要使用-std=c++14 或更高版本进行编译。
  • 是的,我会将其添加到问题中。
  • 您实际上可以比较不同优化级别生成的代码,看看它实际上有什么不同。

标签: c++ clang c++14 compiler-optimization chrono


【解决方案1】:

错误在您的构造函数中:

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

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

我可以方便地访问的 clang 版本无法诊断此错误,但 GCC 版本 5 和 6(在 Linux 上 - 我的 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 version 7.0.2 (clang-700.1.81);它不会改变程序在-O2 的行为。你的 clang 可能会诊断如果您打开警告,则会出现此错误。)

(我已在诊断程序中提交了关于 IR gobbledygook 的 bug report with GCC。)

【讨论】:

  • 啊...我没有费心去初始化它,因为我在start()中为它赋值,但我实际上在start()的开头插入了检查,然后我没有再想了。
  • 为什么这在较低的优化级别不会造成任何麻烦?
  • 无论何种优化过程暴露了问题,都没有在较低的优化级别启用。更详细地回答需要挖掘中间阶段的调试转储,我不知道如何使用 clang 来做到这一点(而且我也没有合适的 clang 版本来做到这一点)。
【解决方案2】:

bool started 未初始化。 如果你把它初始化为false,它可以和-O2一起工作:

live example

您可以使用未定义的行为清理程序找到这样的错误:

$ 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'

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-08-12
    • 1970-01-01
    • 2015-12-25
    • 2012-10-08
    • 1970-01-01
    • 2020-06-23
    • 2021-02-01
    • 2022-07-05
    相关资源
    最近更新 更多