简体   繁体   English

这种优化是否是编译器错误?

[英]Is this kind of optimization a compiler bug or not?

Declarations: I use vs 2010/vs 2013, and clang 3.4 prebuilt binary. 声明:我使用vs 2010 / vs 2013和clang 3.4预先构建的二进制文件。

I've found a bug in our production code. 我在生产代码中发现了一个错误。 I minimize the reproduce code to the following: 我将重现代码最小化为以下内容:

#include <windows.h>
#include <process.h>
#include <stdio.h>
using namespace std;

bool s_begin_init =  false;
bool s_init_done =  false;

void thread_proc(void * arg)
{
    DWORD tid = GetCurrentThreadId();
    printf("Begin Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
    if (!s_begin_init)
    {
        s_begin_init = true;
        Sleep(20);
        s_init_done = true;
    }
    else
    {
        while(!s_init_done) { ; }
    }
    printf("End   Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
}

int main(int argc, char *argv[])
{
    argc = argc                       ; argv = argv                       ; 
    for(int i = 0; i < 30; ++i)
    {
        _beginthread(thread_proc, 0, reinterpret_cast<void*>(i));
    }
    getchar();
    return 0;
}

To compile and run the code: cl /O2 /Zi /Favc.asm vc_O2_bug.cpp && vc_O2_bug.exe 编译并运行代码:cl / O2 / Zi /Favc.asm vc_O2_bug.cpp && vc_O2_bug.exe

Some of the threads are busying in the while loop. 一些线程正在while循环中忙碌。 By checking the produced assembly code, I found the assembly code of 通过检查产生的汇编代码,我找到了

while(!s_init_done) {; while(!s_init_done){; } }

is: 是:

; Line 19
    mov al, BYTE PTR ?s_init_done@@3_NA     ; s_init_done
$LL2@thread_pro:
; Line 21
    test    al, al
    je  SHORT $LL2@thread_pro
; Line 23

It's obvious that when use -O2 optimization flag, VC copy the s_init_done to al register, and repeatedly test the al register. 显然,当使用-O2优化标志时,VC将s_init_done复制到al寄存器,并反复测试al寄存器。

I then use the clang-cl.exe compiler driver to test the code. 然后,我使用clang-cl.exe编译器驱动程序来测试代码。 The result is same, and the assembly code are 结果是一样的,并且汇编代码是
equivalent. 当量。

It looks that the compiler think that variable s_init_done will never be changed because the only statement which change it's value is in the "if" block, which is exclusive with the current "else" branch. 看来编译器认为永远不会更改变量s_init_done,因为唯一更改其值的语句是在“ if”块中,该块与当前“ else”分支互斥。

I tried the same code with VS2013, The result is also same. 我用VS2013尝试了相同的代码,结果也相同。

What I doubt is: In C++98/C++03 standard, there's no concept of thread. 我怀疑的是:在C ++ 98 / C ++ 03标准中,没有线程的概念。 So the compiler can perform such an optimization for a single-thread-machine. 因此,编译器可以对单线程计算机执行这种优化。 But since c++11 has thread, and both clang 3.4 and VC2013 have support C++11 well, do my question is: 但是由于c ++ 11具有线程,并且clang 3.4和VC2013都很好地支持C ++ 11,所以我的问题是:

Is think kind of optimization a compiler bug for C++98/C++03, and for C++11 separately? 认为优化是针对C ++ 98 / C ++ 03和C ++ 11的编译器错误吗?

BTW: When I use -O1 instead, or add volatile qualifier to s_init_done, the bug disappeared. 顺便说一句:当我改用-O1或向s_init_done添加volatile限定符时,该错误消失了。

Your program contains data races on s_begin_init and s_init_done , and therefore has undefined behavior. 您的程序包含s_begin_inits_init_done上的数据s_init_done ,因此具有未定义的行为。 Per C++11 §1.10/21: 根据C ++ 11§1.10/ 21:

The execution of a program contains a data race if it contains two conflicting actions in different threads, at least one of which is not atomic, and neither happens before the other. 如果程序的执行在不同线程中包含两个冲突的动作,则其中至少有一个不是原子的,并且在两个线程之前都没有发生,因此该程序的执行将引起数据争用 Any such data race results in undefined behavior. 任何此类数据争用都会导致未定义的行为。

The fix is to declare both boolean variables to be atomic: 解决方法是将两个布尔变量都声明为原子的:

std::atomic<bool> s_begin_init{false};
std::atomic<bool> s_init_done{false};

or to synchronize accesses to them with a mutex (I'll throw in a condition variable to avoid busy-waiting): 或使用mutex同步对它们的访问(我将抛出一个条件变量以避免繁忙等待):

std::mutex mtx;
std::condition_variable cvar;
bool s_begin_init = false;
bool s_init_done = false;

void thread_proc(void * arg)
{
    DWORD tid = GetCurrentThreadId();
    printf("Begin Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
    std::unique_lock<std::mutex> lock(mtx);
    if (!s_begin_init)
    {
        s_begin_init = true;
        lock.unlock();
        Sleep(20);
        lock.lock();
        s_init_done = true;
        cvar.notify_all();
    }
    else
    {
        while(!s_init_done) { cvar.wait(lock); }
    }
    printf("End   Thread %2d, TID=%u\n", reinterpret_cast<int>(arg), tid);
}

EDIT: I just noticed the mention of VS2010 in the OP. 编辑:我只是注意到在OP中提到VS2010。 VS2010 does not support C++11 atomics , so you will have to use the mutex solution or take advantage of MSVC's non-standard extension that gives volatile variables acquire-release semantics : VS2010不支持C ++ 11原子 ,因此您将不得不使用mutex解决方案或利用MSVC的非标准扩展,该扩展提供了volatile变量的获取-释放语义

volatile bool s_begin_init = false;
volatile bool s_init_done = false;

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM