【问题标题】:Is this kind of optimization a compiler bug or not?这种优化是否是编译器错误?
【发布时间】:2014-06-07 15:25:22
【问题描述】:

声明:我使用 vs 2010/vs 2013 和 clang 3.4 预构建二进制文件。

我在我们的生产代码中发现了一个错误。我将重现代码最小化为:

#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;
}

编译和运行代码: cl /O2 /Zi /Favc.asm vc_O2_bug.cpp && vc_O2_bug.exe

一些线程正忙于 while 循环。通过查看生成的汇编代码,我找到了

的汇编代码

while(!s_init_done) {; }

是:

; 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

很明显,当使用-O2优化标志时,VC将s_init_done复制到al寄存器,并反复测试al寄存器。

然后我使用 clang-cl.exe 编译器驱动程序来测试代码。结果是一样的,汇编代码是
等价的。

看起来编译器认为变量 s_init_done 永远不会改变,因为改变它的值的唯一语句是在“if”块中,它与当前的“else”分支是互斥的。

我用VS2013试过同样的代码,结果也是一样。

我怀疑的是:在 C++98/C++03 标准中,没有线程的概念。所以编译器可以为单线程机器执行这样的优化。但是由于 c++11 有线程,并且 clang 3.4 和 VC2013 都很好地支持了 C++11,所以我的问题是:

认为优化是 C++98/C++03 和 C++11 的编译器错误吗?

顺便说一句:当我改用 -O1 或将 volatile 限定符添加到 s_init_done 时,错误消失了。

【问题讨论】:

  • 这不是编译器错误,您的程序中没有任何内容会强制编译器不根据标准优化静态变量负载。
  • @CaptainObvlious:你确定按照标准就够了吗?
  • @Deduplicator MSVC 为volatile 提供获取-释放语义;这是一个非标准的扩展。数据竞争的标准解决方法是将s_begin_inits_init_done 变成std::atomic&lt;bool&gt;
  • @Deduplicator To force a reload yes but it doesn't address the race condition.
  • 从技术上讲,您的 printf 格式字符串中还有未定义的行为,DWORDtypedefed 为 unsigned long 并且需要 %lu

标签: c++ multithreading visual-c++ c++11 clang


【解决方案1】:

您的程序包含s_begin_inits_init_done 上的数据竞争,因此具有未定义的行为。根据 C++11 §1.10/21:

如果一个程序在不同的线程中包含两个相互冲突的动作,则该程序的执行包含一个数据竞争,其中至少一个不是原子的,并且两者都不会在另一个之前发生。任何此类数据竞争都会导致未定义的行为。

解决方法是将两个布尔变量声明为原子:

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

或使用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);
}

编辑:我刚刚注意到 OP 中提到了 VS2010。 VS2010 does not support C++11 atomics,因此您将不得不使用mutex 解决方案或利用MSVC's non-standard extension that gives volatile variables acquire-release semantics

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

【讨论】:

    猜你喜欢
    • 2017-08-31
    • 1970-01-01
    • 2012-09-28
    • 1970-01-01
    • 2013-07-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-24
    相关资源
    最近更新 更多