C++ 记忆障碍是如何工作的?

C++ 记忆障碍是如何工作的?,c++,c,assembly,execution,instructions,C++,C,Assembly,Execution,Instructions,在Windows下,有三个编译器内部函数来实现内存屏障: 1. _ReadBarrier; 2. _WriteBarrier; 3. _ReadWriteBarrier; 然而,我发现了一个奇怪的问题:_ReadBarrier似乎是一个什么都不做的伪函数!下面是我用VC++2012生成的汇编代码 我的问题是:如何在汇编指令中实现内存屏障功能 int main() { 013EEE10 push ebp 013EEE11 mov ebp,esp

在Windows下,有三个编译器内部函数来实现内存屏障:

1. _ReadBarrier;

2. _WriteBarrier;

3. _ReadWriteBarrier;
然而,我发现了一个奇怪的问题:_ReadBarrier似乎是一个什么都不做的伪函数!下面是我用VC++2012生成的汇编代码

我的问题是:如何在汇编指令中实现内存屏障功能

int main()
{   
013EEE10  push        ebp  
013EEE11  mov         ebp,esp  
013EEE13  sub         esp,0CCh  
013EEE19  push        ebx  
013EEE1A  push        esi  
013EEE1B  push        edi  
013EEE1C  lea         edi,[ebp-0CCh]  
013EEE22  mov         ecx,33h  
013EEE27  mov         eax,0CCCCCCCCh  
013EEE2C  rep stos    dword ptr es:[edi]  
    int n = 0;
013EEE2E  mov         dword ptr [n],0  
    n = n + 1;
013EEE35  mov         eax,dword ptr [n]  
013EEE38  add         eax,1  
013EEE3B  mov         dword ptr [n],eax  
    _ReadBarrier();
    n = n + 1;
013EEE3E  mov         eax,dword ptr [n]  
013EEE41  add         eax,1  
013EEE44  mov         dword ptr [n],eax 
}
013EEE56  xor         eax,eax  
013EEE58  pop         edi  
013EEE59  pop         esi  
013EEE5A  pop         ebx  
013EEE5B  add         esp,0CCh  
013EEE61  cmp         ebp,esp  
013EEE63  call        __RTC_CheckEsp (013EC3B0h)  
013EEE68  mov         esp,ebp  
013EEE6A  pop         ebp  
013EEE6B  ret 

有几个重要的问题要考虑。也许是 首先,屏障只在多线程中起作用 大多数编译器需要一个特殊的选项来生成 多线程代码。而像
\u ReadBarrier
这样的东西几乎是 当然,编译器内置,除非 您已经给出了多线程代码的选项

第二个是硬件所需要的,即使是在 多线程上下文不同。在我见过的大多数机器上 在这台机器上工作了(四十多年),从来都不需要 任何东西仅当机器已关闭时,屏障才相关 复杂的读写管道。(大多数早期机器 甚至没有围栏或屏障说明,因此生成
代码必须是空的。)

\u ReadBarrier
\u WriteBarrier
\u ReadWriteBarrier
是;它们与CPU内存屏障完全无关,只对特定类型的内存有效(请参阅“受影响的内存”)


是用于强制设置CPU内存屏障的固有属性。然而,微软的建议是在VC++中使用
std::atomic

现代处理器能够执行的指令远远超过它实际“完成”指令的位置,因此,当涉及到某些类型的内存操作时,内存屏障被用来防止它向前运行,在这些操作中,需要严格的排序——对于大多数情况,实际上,在变量b之前写入变量a,或者在变量a之前写入变量b并不重要。但有时确实如此

x86指令集具有
lfence
sfence
fence
,它们是“fence in”分别加载、存储和所有内存操作的指令。关于“栅栏”或“屏障”指令的要点是确保屏障指令之前的所有指令在屏障后的下一条指令可以继续之前已完成加载、存储或两者

如果您正在实现例如信号量、互斥量或类似的指令,这一点很重要,因为在继续读取其他数据之前,存储表示“我已锁定信号量”的值很重要。否则事情可能会出问题,比如说

请注意,除非您真的知道如何使用内存屏障,否则最好不要使用它们——并且依赖于已经存在的解决相同问题的代码——
std::atomic
是资助此类代码的一个地方。我已经写了很多“棘手”的代码,但只有一两次我需要在代码中设置内存障碍


有好几次,我需要使编译器不散布代码,这可以通过“无操作函数”来实现,显然现在甚至有特殊的内在函数来实现

在x86上,所有加载都是获取,所有存储都是发布,因此不需要任何显式代码。唯一必要的代码是完整的屏障。读屏障的概念比x86体系结构更为普遍,在内存模型较弱的机器上也很重要。我猜这些只是改变编译器生成代码方式的编译器屏障,而不是汇编级屏障。@KerrekSB即使是最新的机器,这真的是真的吗?这样做会大大降低速度,并消除使用多核的许多效用(至少对于某些类型的应用程序是如此)。@JamesKanze:Hm,至少就我所知。不过,我完全有可能弄错了。linux内核smp内存障碍的一个很好的解释也与低级硬件访问相关。@edgar.holleis是真的。当然,这不会影响用户进程,但真正的标准不是线程,而是两个实体访问内存的可能性。线程只是发生这种情况的最常见的情况。