为什么 clang 会优化轮询另一个线程写入的变量的循环?

ros*_*573 2 c++ optimization multithreading clang data-race

当我学习 C++ 时,我发现了一些奇怪的东西......
我认为下面的代码会产生大数的结果(至少不是 1.1)。
相反,结果是在此处输入图像描述

其他编译器按预期工作。
但是具有积极优化的 clang 编译器似乎忽略了 while 循环。
所以我的问题是,我的代码有什么问题?还是clang有意为之?

我使用了apple clang编译器(v14.0.3)

#include <iostream>
#include <thread>


static bool should_terminate = false;

void infinite_loop() {
    long double i = 1.1;
    while(!should_terminate)
        i *= i;
    std::cout << i;
}

int main() {
    std::thread(infinite_loop).detach();
    std::cout << "main thread";
    for (int i = 0 ; i < 5; i++) {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        std::cout << ".";
    }
    should_terminate = true;
}
Run Code Online (Sandbox Code Playgroud)

来自编译器资源管理器的汇编结果(clang v16.0.0,-O3)
这似乎也跳过了 while 循环。

_Z13infinite_loopv:                     # @_Z13infinite_loopv
        sub     rsp, 24
        fld     qword ptr [rip + .LCPI0_0]
        fstp    tbyte ptr [rsp]
        mov     rdi, qword ptr [rip + _ZSt4cout@GOTPCREL]
        call    _ZNSo9_M_insertIeEERSoT_@PLT
        add     rsp, 24
        ret
Run Code Online (Sandbox Code Playgroud)

use*_*522 6

您的代码有未定义的行为:

should_terminate不是原子对象,因此在一个线程中写入它并在另一个线程中潜在地并发(即没有任何同步)访问它是一种数据竞争,这始终是未定义的行为。

实际上,这个 UB 规则允许编译器准确地进行您在此处看到的优化。

编译器可以假设should_terminate在循环中永远不会改变,因为它不可能从另一个线程写入,因为这将是数据竞争。因此,当到达循环时,它要么是false且 保持false,以便循环永远不会终止,要么是true,在这种情况下循环体根本不执行。

然后,因为不执行任何原子/IO/易失性/同步操作的无限循环也会有 UB,所以编译器可以进一步推断,当到达循环时,它should_terminate必须(总是) 。true因此,循环体永远不会被执行,并且删除循环是允许的优化。

所以 Clang 在这里表现正确,而你的期望是错误的。should_terminate必须是std::atomic<bool>(或std::atomic_flag),以便与其他访问不同步地写入它,这不是数据争用。