C++ 这种优化是不是编译器的错误?
声明:我使用VS2010/VS2013和Clang3.4预构建二进制文件 我在我们的产品代码中发现了一个bug。我将复制代码最小化为以下内容:C++ 这种优化是不是编译器的错误?,c++,multithreading,visual-c++,c++11,clang,C++,Multithreading,Visual C++,C++11,Clang,声明:我使用VS2010/VS2013和Clang3.4预构建二进制文件 我在我们的产品代码中发现了一个bug。我将复制代码最小化为以下内容: #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
#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;
}
很明显,当使用-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时,错误消失了。您的程序包含s_begin_init和s_init_done上的数据竞争,因此具有未定义的行为。根据C++11§1.10/21: 如果一个程序在不同的线程中包含两个冲突的操作,则该程序的执行包含一个数据竞争,其中至少一个操作不是原子的,并且两个操作都不在另一个线程之前发生。任何这样的数据竞争都会导致未定义的行为 修复方法是将两个布尔变量声明为原子变量:
std::atomic<bool> s_begin_init{false};
std::atomic<bool> s_init_done{false};
编辑:我刚刚注意到OP中提到了VS2010,因此您必须使用互斥解决方案或利用:
这不是一个编译器错误,你的程序中没有任何东西会迫使编译器不按照标准优化静态变量负载。@CaptainObvlious:根据标准,你真的确定这足够了吗?@Deduplicator MSVC为volatile提供了获取释放语义;这是一个非标准的扩展。数据竞争的标准解决方案是将s_begin_init和s_init_done放入std::atomic。@重复数据消除程序强制重新加载yes,但它不能解决竞争条件。从技术上讲,printf格式字符串中也有未定义的行为,DWORD类型定义为无符号长,需要%lu。
std::atomic<bool> s_begin_init{false};
std::atomic<bool> s_init_done{false};
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);
}
volatile bool s_begin_init = false;
volatile bool s_init_done = false;