为什么现代编译器针对 C++11 及更高版本对此进行了优化

Bar*_*art 3 c++

我迷路了..我想使用编译器资源管理器来试验多线程 C 代码,并从一段简单的代码开始。该代码是用-O3.

static int hang = 0;

void set_hang(int x) {
    hang = x;
}

void wait() {
    while (hang != 0) {
        // wait...
    }
}
Run Code Online (Sandbox Code Playgroud)

令我惊讶的是,这是编译器的输出:

set_hang(int):
        mov     dword ptr [rip + hang], edi
        ret
wait():
        ret
Run Code Online (Sandbox Code Playgroud)

我花了一段时间才注意到我正在将代码编译为 C++ 而不是 C。切换到 C 给了我一些我所期望的东西:

set_hang:
        mov     DWORD PTR hang[rip], edi
        ret
wait:
        mov     eax, DWORD PTR hang[rip]
        test    eax, eax
        je      .L3
.L5:
        jmp     .L5
.L3:
        ret
Run Code Online (Sandbox Code Playgroud)

因此,当编译为 C++ 时,wait()无论之前将哪个值传递给 ,总是返回set_hang()。我通过在我的电脑上编译代码来确认这一点。该代码立即存在,而我希望它永远挂起:

int main(void) {
    set_hang(1);
    wait();
    return 0;
}   
Run Code Online (Sandbox Code Playgroud)

事实上,如果我使用gcc而不是with 来编译它g++,它就会挂起。

我尝试了不同的编译器(Clang 和 GCC),这只发生在 Clang 12.0.0 或 higer 或 GCC 10.1 或更高版本上。如果我--std=c++98也通过了我期望的代码,那么它似乎是 C++11 及更高版本特有的代码。

static从 中删除关键字hang不会影响发出的程序集。

这里发生了什么?我写 C++ 已经有几个月了,所以我可能会错过一些关于最新、最伟大的外来 C++ 黑魔法的知识,但这确实是简单的代码。我一无所知。


编辑:即使这个程序也被完全优化掉了:

// test.cpp
static int hang = 0;

static void set_hang(int x) {
    hang = x;
}

static void wait() {
    while (hang != 0) {
        // wait...
    }
}

int main(void) {
    set_hang(1);
    wait();
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

编译器输出:

main:
        xor     eax, eax
        ret
Run Code Online (Sandbox Code Playgroud)

对于 Ubuntu 上的 GCC 版本 10.3.0:

该命令将挂起:g++ -O1 -o test test.cpp && ./test

这个命令不会:g++ -O2 -o test test.cpp && ./test

eer*_*ika 6

这是因为以下规则:

[介绍.进展]

该实现可能假设任何线程最终都会执行以下操作之一:

  • 终止,
  • 调用库 I/O 函数,
  • 通过易失性左值执行访问,或者
  • 执行同步操作或原子操作。

编译器能够证明进入循环的程序永远不会执行任何列出的操作,因此可以假设永远不会进入循环。